1<?php 2 3/* 4 * This file is part of MailSo. 5 * 6 * (c) 2014 Usenko Timur 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace MailSo\Imap; 13 14/** 15 * @category MailSo 16 * @package Imap 17 */ 18class ImapClient extends \MailSo\Net\NetClient 19{ 20 /** 21 * @var string 22 */ 23 const TAG_PREFIX = 'TAG'; 24 25 /** 26 * @var int 27 */ 28 private $iResponseBufParsedPos; 29 30 /** 31 * @var int 32 */ 33 private $iTagCount; 34 35 /** 36 * @var array 37 */ 38 private $aCapabilityItems; 39 40 /** 41 * @var \MailSo\Imap\FolderInformation 42 */ 43 private $oCurrentFolderInfo; 44 45 /** 46 * @var array 47 */ 48 private $aLastResponse; 49 50 /** 51 * @var array 52 */ 53 private $aFetchCallbacks; 54 55 /** 56 * @var bool 57 */ 58 private $bNeedNext; 59 60 /** 61 * @var array 62 */ 63 private $aPartialResponses; 64 65 /** 66 * @var array 67 */ 68 private $aTagTimeouts; 69 70 /** 71 * @var bool 72 */ 73 private $bIsLoggined; 74 75 /** 76 * @var bool 77 */ 78 private $bIsSelected; 79 80 /** 81 * @var string 82 */ 83 private $sLogginedUser; 84 85 /** 86 * @var bool 87 */ 88 public $__FORCE_SELECT_ON_EXAMINE__; 89 90 /** 91 * @access protected 92 */ 93 protected function __construct() 94 { 95 parent::__construct(); 96 97 $this->iTagCount = 0; 98 $this->aCapabilityItems = null; 99 $this->oCurrentFolderInfo = null; 100 $this->aFetchCallbacks = null; 101 $this->iResponseBufParsedPos = 0; 102 103 $this->aLastResponse = array(); 104 $this->bNeedNext = true; 105 $this->aPartialResponses = array(); 106 107 $this->aTagTimeouts = array(); 108 109 $this->bIsLoggined = false; 110 $this->bIsSelected = false; 111 $this->sLogginedUser = ''; 112 113 $this->__FORCE_SELECT_ON_EXAMINE__ = false; 114 115 @\ini_set('xdebug.max_nesting_level', 500); 116 } 117 118 /** 119 * @return \MailSo\Imap\ImapClient 120 */ 121 public static function NewInstance() 122 { 123 return new self(); 124 } 125 126 /** 127 * @return string 128 */ 129 public function GetLogginedUser() 130 { 131 return $this->sLogginedUser; 132 } 133 134 /** 135 * @param string $sServerName 136 * @param int $iPort = 143 137 * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT 138 * @param bool $bVerifySsl = false 139 * @param bool $bAllowSelfSigned = true 140 * @param string $sClientCert = '' 141 * 142 * @return \MailSo\Imap\ImapClient 143 * 144 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 145 * @throws \MailSo\Net\Exceptions\Exception 146 * @throws \MailSo\Imap\Exceptions\Exception 147 */ 148 public function Connect($sServerName, $iPort = 143, 149 $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, 150 $bVerifySsl = false, $bAllowSelfSigned = true, 151 $sClientCert = '') 152 { 153 $this->aTagTimeouts['*'] = \microtime(true); 154 155 parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned, $sClientCert); 156 157 $this->parseResponseWithValidation('*', true); 158 159 if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( 160 $this->IsSupported('STARTTLS'), $this->iSecurityType)) 161 { 162 $this->SendRequestWithCheck('STARTTLS'); 163 $this->EnableCrypto(); 164 165 $this->aCapabilityItems = null; 166 } 167 else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType) 168 { 169 $this->writeLogException( 170 new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'), 171 \MailSo\Log\Enumerations\Type::ERROR, true); 172 } 173 174 return $this; 175 } 176 177 protected function _xor($string, $string2) 178 { 179 $result = ''; 180 $size = strlen($string); 181 for ($i=0; $i<$size; $i++) { 182 $result .= chr(ord($string[$i]) ^ ord($string2[$i])); 183 } 184 return $result; 185 } 186 187 /** 188 * @param string $sLogin 189 * @param string $sPassword 190 * @param string $sProxyAuthUser = '' 191 * @param bool $bUseAuthPlainIfSupported = true 192 * @param bool $bUseAuthCramMd5IfSupported = true 193 * 194 * @return \MailSo\Imap\ImapClient 195 * 196 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 197 * @throws \MailSo\Net\Exceptions\Exception 198 * @throws \MailSo\Imap\Exceptions\Exception 199 */ 200 public function Login($sLogin, $sPassword, $sProxyAuthUser = '', 201 $bUseAuthPlainIfSupported = true, $bUseAuthCramMd5IfSupported = true) 202 { 203 if (!\MailSo\Base\Validator::NotEmptyString($sLogin, true) || 204 !\MailSo\Base\Validator::NotEmptyString($sPassword, true)) 205 { 206 $this->writeLogException( 207 new \MailSo\Base\Exceptions\InvalidArgumentException(), 208 \MailSo\Log\Enumerations\Type::ERROR, true); 209 } 210 211 $sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin)); 212 213 $sPassword = $sPassword; 214 215 $this->sLogginedUser = $sLogin; 216 217 try 218 { 219 if ($bUseAuthCramMd5IfSupported && $this->IsSupported('AUTH=CRAM-MD5')) 220 { 221 $this->SendRequest('AUTHENTICATE', array('CRAM-MD5')); 222 223 $aResponse = $this->parseResponseWithValidation(); 224 if ($aResponse && \is_array($aResponse) && 0 < \count($aResponse) && 225 \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $aResponse[\count($aResponse) - 1]->ResponseType) 226 { 227 $oContinuationResponse = null; 228 foreach ($aResponse as $oResponse) 229 { 230 if ($oResponse && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oResponse->ResponseType) 231 { 232 $oContinuationResponse = $oResponse; 233 } 234 } 235 236 if ($oContinuationResponse && !empty($oContinuationResponse->ResponseList[1])) 237 { 238 $sTicket = @\base64_decode($oContinuationResponse->ResponseList[1]); 239 $this->oLogger->Write('ticket: '.$sTicket); 240 241 $sToken = \base64_encode($sLogin.' '.\MailSo\Base\Utils::Hmac($sTicket, $sPassword)); 242 243 if ($this->oLogger) 244 { 245 $this->oLogger->AddSecret($sToken); 246 } 247 248 $this->sendRaw($sToken, true, '*******'); 249 $this->parseResponseWithValidation(); 250 } 251 else 252 { 253 $this->writeLogException( 254 new \MailSo\Imap\Exceptions\LoginException(), 255 \MailSo\Log\Enumerations\Type::NOTICE, true); 256 } 257 } 258 else 259 { 260 $this->writeLogException( 261 new \MailSo\Imap\Exceptions\LoginException(), 262 \MailSo\Log\Enumerations\Type::NOTICE, true); 263 } 264 } 265 else if ($bUseAuthPlainIfSupported && $this->IsSupported('AUTH=PLAIN')) 266 { 267 $sToken = \base64_encode("\0".$sLogin."\0".$sPassword); 268 if ($this->oLogger) 269 { 270 $this->oLogger->AddSecret($sToken); 271 } 272 273 if ($this->IsSupported('AUTH=SASL-IR') && false) 274 { 275 $this->SendRequestWithCheck('AUTHENTICATE', array('PLAIN', $sToken)); 276 } 277 else 278 { 279 $this->SendRequest('AUTHENTICATE', array('PLAIN')); 280 $this->parseResponseWithValidation(); 281 282 $this->sendRaw($sToken, true, '*******'); 283 $this->parseResponseWithValidation(); 284 } 285 } 286 else 287 { 288 if ($this->oLogger) 289 { 290 $this->oLogger->AddSecret($this->EscapeString($sPassword)); 291 } 292 293 $this->SendRequestWithCheck('LOGIN', 294 array( 295 $this->EscapeString($sLogin), 296 $this->EscapeString($sPassword) 297 )); 298 } 299// else 300// { 301// $this->writeLogException( 302// new \MailSo\Imap\Exceptions\LoginBadMethodException(), 303// \MailSo\Log\Enumerations\Type::NOTICE, true); 304// } 305 306 if (0 < \strlen($sProxyAuthUser)) 307 { 308 $this->SendRequestWithCheck('PROXYAUTH', array($this->EscapeString($sProxyAuthUser))); 309 } 310 } 311 catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) 312 { 313 $this->writeLogException( 314 new \MailSo\Imap\Exceptions\LoginBadCredentialsException( 315 $oException->GetResponses(), '', 0, $oException), 316 \MailSo\Log\Enumerations\Type::NOTICE, true); 317 } 318 319 $this->bIsLoggined = true; 320 $this->aCapabilityItems = null; 321 322 return $this; 323 } 324 325 /** 326 * @param string $sXOAuth2Token 327 * 328 * @return \MailSo\Imap\ImapClient 329 * 330 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 331 * @throws \MailSo\Net\Exceptions\Exception 332 * @throws \MailSo\Imap\Exceptions\Exception 333 */ 334 public function LoginWithXOauth2($sXOAuth2Token) 335 { 336 if (!\MailSo\Base\Validator::NotEmptyString($sXOAuth2Token, true)) 337 { 338 $this->writeLogException( 339 new \MailSo\Base\Exceptions\InvalidArgumentException(), 340 \MailSo\Log\Enumerations\Type::ERROR, true); 341 } 342 343 if (!$this->IsSupported('AUTH=XOAUTH2')) 344 { 345 $this->writeLogException( 346 new \MailSo\Imap\Exceptions\LoginBadMethodException(), 347 \MailSo\Log\Enumerations\Type::NOTICE, true); 348 } 349 350 try 351 { 352 $this->SendRequest('AUTHENTICATE', array('XOAUTH2', \trim($sXOAuth2Token))); 353 $aR = $this->parseResponseWithValidation(); 354 355 if (\is_array($aR) && 0 < \count($aR) && isset($aR[\count($aR) - 1])) 356 { 357 $oR = $aR[\count($aR) - 1]; 358 if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oR->ResponseType) 359 { 360 if (!empty($oR->ResponseList[1]) && preg_match('/^[a-zA-Z0-9=+\/]+$/', $oR->ResponseList[1])) 361 { 362 $this->Logger()->Write(\base64_decode($oR->ResponseList[1]), 363 \MailSo\Log\Enumerations\Type::WARNING); 364 } 365 366 $this->sendRaw(''); 367 $this->parseResponseWithValidation(); 368 } 369 } 370 } 371 catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) 372 { 373 $this->writeLogException( 374 new \MailSo\Imap\Exceptions\LoginBadCredentialsException( 375 $oException->GetResponses(), '', 0, $oException), 376 \MailSo\Log\Enumerations\Type::NOTICE, true); 377 } 378 379 $this->bIsLoggined = true; 380 $this->aCapabilityItems = null; 381 382 return $this; 383 } 384 385 /** 386 * @return \MailSo\Imap\ImapClient 387 * 388 * @throws \MailSo\Net\Exceptions\Exception 389 */ 390 public function Logout() 391 { 392 if ($this->bIsLoggined) 393 { 394 $this->bIsLoggined = false; 395 $this->SendRequestWithCheck('LOGOUT', array()); 396 } 397 398 return $this; 399 } 400 401 /** 402 * @return \MailSo\Imap\ImapClient 403 */ 404 public function ForceCloseConnection() 405 { 406 $this->Disconnect(); 407 408 return $this; 409 } 410 411 /** 412 * @return bool 413 */ 414 public function IsLoggined() 415 { 416 return $this->IsConnected() && $this->bIsLoggined; 417 } 418 419 /** 420 * @return bool 421 */ 422 public function IsSelected() 423 { 424 return $this->IsLoggined() && $this->bIsSelected; 425 } 426 427 /** 428 * @return array|null 429 * 430 * @throws \MailSo\Net\Exceptions\Exception 431 * @throws \MailSo\Imap\Exceptions\Exception 432 */ 433 public function Capability() 434 { 435 $this->SendRequestWithCheck('CAPABILITY', array(), true); 436 return $this->aCapabilityItems; 437 } 438 439 /** 440 * @param string $sExtentionName 441 * @return bool 442 * 443 * @throws \MailSo\Net\Exceptions\Exception 444 * @throws \MailSo\Imap\Exceptions\Exception 445 */ 446 public function IsSupported($sExtentionName) 447 { 448 $bResult = \MailSo\Base\Validator::NotEmptyString($sExtentionName, true); 449 if ($bResult && null === $this->aCapabilityItems) 450 { 451 $this->aCapabilityItems = $this->Capability(); 452 } 453 454 return $bResult && \is_array($this->aCapabilityItems) && 455 \in_array(\strtoupper($sExtentionName), $this->aCapabilityItems); 456 } 457 458 /** 459 * @return \MailSo\Imap\NamespaceResult|null 460 * 461 * @throws \MailSo\Net\Exceptions\Exception 462 * @throws \MailSo\Imap\Exceptions\Exception 463 */ 464 public function GetNamespace() 465 { 466 if (!$this->IsSupported('NAMESPACE')) 467 { 468 return null; 469 } 470 471 $oReturn = false; 472 473 $this->SendRequest('NAMESPACE'); 474 $aResult = $this->parseResponseWithValidation(); 475 476 $oImapResponse = null; 477 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 478 { 479 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && 480 'NAMESPACE' === $oImapResponse->StatusOrIndex) 481 { 482 $oReturn = NamespaceResult::NewInstance(); 483 $oReturn->InitByImapResponse($oImapResponse); 484 break; 485 } 486 } 487 488 if (false === $oReturn) 489 { 490 $this->writeLogException( 491 new \MailSo\Imap\Exceptions\ResponseException(), 492 \MailSo\Log\Enumerations\Type::ERROR, true); 493 } 494 495 return $oReturn; 496 } 497 498 /** 499 * @return \MailSo\Imap\ImapClient 500 * 501 * @throws \MailSo\Net\Exceptions\Exception 502 * @throws \MailSo\Imap\Exceptions\Exception 503 */ 504 public function Noop() 505 { 506 return $this->SendRequestWithCheck('NOOP'); 507 } 508 509 /** 510 * @param string $sFolderName 511 * 512 * @return \MailSo\Imap\ImapClient 513 * 514 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 515 * @throws \MailSo\Net\Exceptions\Exception 516 * @throws \MailSo\Imap\Exceptions\Exception 517 */ 518 public function FolderCreate($sFolderName) 519 { 520 return $this->SendRequestWithCheck('CREATE', 521 array($this->EscapeString($sFolderName))); 522 } 523 524 /** 525 * @param string $sFolderName 526 * 527 * @return \MailSo\Imap\ImapClient 528 * 529 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 530 * @throws \MailSo\Net\Exceptions\Exception 531 * @throws \MailSo\Imap\Exceptions\Exception 532 */ 533 public function FolderDelete($sFolderName) 534 { 535 return $this->SendRequestWithCheck('DELETE', 536 array($this->EscapeString($sFolderName))); 537 } 538 539 /** 540 * @param string $sFolderName 541 * 542 * @return \MailSo\Imap\ImapClient 543 * 544 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 545 * @throws \MailSo\Net\Exceptions\Exception 546 * @throws \MailSo\Imap\Exceptions\Exception 547 */ 548 public function FolderSubscribe($sFolderName) 549 { 550 return $this->SendRequestWithCheck('SUBSCRIBE', 551 array($this->EscapeString($sFolderName))); 552 } 553 554 /** 555 * @param string $sFolderName 556 * 557 * @return \MailSo\Imap\ImapClient 558 * 559 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 560 * @throws \MailSo\Net\Exceptions\Exception 561 * @throws \MailSo\Imap\Exceptions\Exception 562 */ 563 public function FolderUnSubscribe($sFolderName) 564 { 565 return $this->SendRequestWithCheck('UNSUBSCRIBE', 566 array($this->EscapeString($sFolderName))); 567 } 568 569 /** 570 * @param string $sOldFolderName 571 * @param string $sNewFolderName 572 * 573 * @return \MailSo\Imap\ImapClient 574 * 575 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 576 * @throws \MailSo\Net\Exceptions\Exception 577 * @throws \MailSo\Imap\Exceptions\Exception 578 */ 579 public function FolderRename($sOldFolderName, $sNewFolderName) 580 { 581 return $this->SendRequestWithCheck('RENAME', array( 582 $this->EscapeString($sOldFolderName), 583 $this->EscapeString($sNewFolderName))); 584 } 585 586 /** 587 * @param array $aResult 588 * 589 * @return array 590 */ 591 protected function getStatusFolderInformation($aResult) 592 { 593 $aReturn = array(); 594 595 if (\is_array($aResult)) 596 { 597 $oImapResponse = null; 598 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 599 { 600 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && 601 'STATUS' === $oImapResponse->StatusOrIndex && isset($oImapResponse->ResponseList[3]) && 602 \is_array($oImapResponse->ResponseList[3])) 603 { 604 $sName = null; 605 foreach ($oImapResponse->ResponseList[3] as $sArrayItem) 606 { 607 if (null === $sName) 608 { 609 $sName = $sArrayItem; 610 } 611 else 612 { 613 $aReturn[$sName] = $sArrayItem; 614 $sName = null; 615 } 616 } 617 } 618 } 619 } 620 621 return $aReturn; 622 } 623 624 /** 625 * @param string $sFolderName 626 * @param array $aStatusItems 627 * 628 * @return array|bool 629 * 630 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 631 * @throws \MailSo\Net\Exceptions\Exception 632 * @throws \MailSo\Imap\Exceptions\Exception 633 */ 634 public function FolderStatus($sFolderName, array $aStatusItems) 635 { 636 $aResult = false; 637 if (\count($aStatusItems) > 0) 638 { 639 $this->SendRequest('STATUS', 640 array($this->EscapeString($sFolderName), $aStatusItems)); 641 642 $aResult = $this->getStatusFolderInformation( 643 $this->parseResponseWithValidation()); 644 } 645 646 return $aResult; 647 } 648 649 /** 650 * @param array|string $mName 651 * 652 * @return string 653 */ 654 private function getArrayNameToStringName($mName) 655 { 656 if (\is_string($mName)) 657 { 658 return $mName; 659 } 660 661 if (\is_array($mName)) 662 { 663 if (0 === \count($mName)) 664 { 665 return '[]'; 666 } 667 668 foreach ($mName as &$mSubName) 669 { 670 $mSubName = "[{$this->getArrayNameToStringName($mSubName)}]"; 671 } 672 673 return \implode('', $mName); 674 } 675 676 return ''; 677 } 678 679 /** 680 * @param array $aResult 681 * @param string $sStatus 682 * @param bool $bUseListStatus = false 683 * 684 * @return array 685 */ 686 private function getFoldersFromResult(array $aResult, $sStatus, $bUseListStatus = false) 687 { 688 $aReturn = array(); 689 690 $sDelimiter = ''; 691 $bInbox = false; 692 693 $oImapResponse = null; 694 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 695 { 696 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && 697 $sStatus === $oImapResponse->StatusOrIndex && 5 <= count($oImapResponse->ResponseList)) 698 { 699 try 700 { 701 /** 702 * A bug in the parser converts folder names that start with '[' into arrays, 703 * and subfolders are in $oImapResponse->ResponseList[5+] 704 * https://github.com/the-djmaze/snappymail/issues/1 705 * https://github.com/the-djmaze/snappymail/issues/70 706 * https://github.com/RainLoop/rainloop-webmail/issues/2037 707 */ 708 $aFullNameRawList = \array_slice($oImapResponse->ResponseList, 4); 709 foreach ($aFullNameRawList as &$sName) 710 { 711 $sName = $this->getArrayNameToStringName($sName); 712 } 713 714 $sFullNameRaw = \implode('', $aFullNameRawList); 715 716 $oFolder = Folder::NewInstance($sFullNameRaw, 717 $oImapResponse->ResponseList[3], $oImapResponse->ResponseList[2]); 718 719 if ($oFolder->IsInbox()) 720 { 721 $bInbox = true; 722 } 723 724 if (empty($sDelimiter)) 725 { 726 $sDelimiter = $oFolder->Delimiter(); 727 } 728 729 $aReturn[] = $oFolder; 730 } 731 catch (\MailSo\Base\Exceptions\InvalidArgumentException $oException) 732 { 733 $this->writeLogException($oException, \MailSo\Log\Enumerations\Type::WARNING, false); 734 } 735 } 736 } 737 738 if (!$bInbox && !empty($sDelimiter)) 739 { 740 $aReturn[] = Folder::NewInstance('INBOX', $sDelimiter); 741 } 742 743 if ($bUseListStatus) 744 { 745 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 746 { 747 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && 748 'STATUS' === $oImapResponse->StatusOrIndex && 749 isset($oImapResponse->ResponseList[2]) && 750 isset($oImapResponse->ResponseList[3]) && 751 \is_array($oImapResponse->ResponseList[3])) 752 { 753 $sFolderNameRaw = $oImapResponse->ResponseList[2]; 754 755 $oCurrentFolder = null; 756 foreach ($aReturn as &$oFolder) 757 { 758 if ($oFolder && $sFolderNameRaw === $oFolder->FullNameRaw()) 759 { 760 $oCurrentFolder =& $oFolder; 761 break; 762 } 763 } 764 765 if (null !== $oCurrentFolder) 766 { 767 $sName = null; 768 $aStatus = array(); 769 770 foreach ($oImapResponse->ResponseList[3] as $sArrayItem) 771 { 772 if (null === $sName) 773 { 774 $sName = $sArrayItem; 775 } 776 else 777 { 778 $aStatus[$sName] = $sArrayItem; 779 $sName = null; 780 } 781 } 782 783 if (0 < count($aStatus)) 784 { 785 $oCurrentFolder->SetExtended('STATUS', $aStatus); 786 } 787 } 788 789 unset($oCurrentFolder); 790 } 791 } 792 } 793 794 return $aReturn; 795 } 796 797 /** 798 * @param bool $bIsSubscribeList 799 * @param string $sParentFolderName = '' 800 * @param string $sListPattern = '*' 801 * @param bool $bUseListStatus = false 802 * 803 * @return array 804 * 805 * @throws \MailSo\Net\Exceptions\Exception 806 * @throws \MailSo\Imap\Exceptions\Exception 807 */ 808 private function specificFolderList($bIsSubscribeList, $sParentFolderName = '', $sListPattern = '*', $bUseListStatus = false) 809 { 810 $sCmd = 'LSUB'; 811 if (!$bIsSubscribeList) 812 { 813 $sCmd = 'LIST'; 814 } 815 816 $sListPattern = 0 === strlen(trim($sListPattern)) ? '*' : $sListPattern; 817 818 $aParameters = array( 819 $this->EscapeString($sParentFolderName), 820 $this->EscapeString($sListPattern) 821 ); 822 823 if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS')) 824 { 825 $aL = array( 826 \MailSo\Imap\Enumerations\FolderStatus::MESSAGES, 827 \MailSo\Imap\Enumerations\FolderStatus::UNSEEN, 828 \MailSo\Imap\Enumerations\FolderStatus::UIDNEXT 829 ); 830 831// if ($this->IsSupported('CONDSTORE')) 832// { 833// $aL[] = \MailSo\Imap\Enumerations\FolderStatus::HIGHESTMODSEQ; 834// } 835 836 $aParameters[] = 'RETURN'; 837 $aParameters[] = array('STATUS', $aL); 838 } 839 else 840 { 841 $bUseListStatus = false; 842 } 843 844 $this->SendRequest($sCmd, $aParameters); 845 846 return $this->getFoldersFromResult( 847 $this->parseResponseWithValidation(), $sCmd, $bUseListStatus); 848 } 849 850 /** 851 * @param string $sParentFolderName = '' 852 * @param string $sListPattern = '*' 853 * 854 * @return array 855 * 856 * @throws \MailSo\Net\Exceptions\Exception 857 * @throws \MailSo\Imap\Exceptions\Exception 858 */ 859 public function FolderList($sParentFolderName = '', $sListPattern = '*') 860 { 861 return $this->specificFolderList(false, $sParentFolderName, $sListPattern); 862 } 863 864 /** 865 * @param string $sParentFolderName = '' 866 * @param string $sListPattern = '*' 867 * 868 * @return array 869 * 870 * @throws \MailSo\Net\Exceptions\Exception 871 * @throws \MailSo\Imap\Exceptions\Exception 872 */ 873 public function FolderSubscribeList($sParentFolderName = '', $sListPattern = '*') 874 { 875 return $this->specificFolderList(true, $sParentFolderName, $sListPattern); 876 } 877 878 /** 879 * @param string $sParentFolderName = '' 880 * @param string $sListPattern = '*' 881 * 882 * @return array 883 * 884 * @throws \MailSo\Net\Exceptions\Exception 885 * @throws \MailSo\Imap\Exceptions\Exception 886 */ 887 public function FolderStatusList($sParentFolderName = '', $sListPattern = '*') 888 { 889 return $this->specificFolderList(false, $sParentFolderName, $sListPattern, true); 890 } 891 892 /** 893 * @param array $aResult 894 * @param string $sFolderName 895 * @param bool $bIsWritable 896 * 897 * @return void 898 */ 899 protected function initCurrentFolderInformation($aResult, $sFolderName, $bIsWritable) 900 { 901 if (\is_array($aResult)) 902 { 903 $oImapResponse = null; 904 $oResult = FolderInformation::NewInstance($sFolderName, $bIsWritable); 905 906 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 907 { 908 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType) 909 { 910 if (\count($oImapResponse->ResponseList) > 2 && 911 'FLAGS' === $oImapResponse->ResponseList[1] && \is_array($oImapResponse->ResponseList[2])) 912 { 913 $oResult->Flags = $oImapResponse->ResponseList[2]; 914 } 915 916 if (is_array($oImapResponse->OptionalResponse) && \count($oImapResponse->OptionalResponse) > 1) 917 { 918 if ('PERMANENTFLAGS' === $oImapResponse->OptionalResponse[0] && 919 is_array($oImapResponse->OptionalResponse[1])) 920 { 921 $oResult->PermanentFlags = $oImapResponse->OptionalResponse[1]; 922 } 923 else if ('UIDVALIDITY' === $oImapResponse->OptionalResponse[0] && 924 isset($oImapResponse->OptionalResponse[1])) 925 { 926 $oResult->Uidvalidity = $oImapResponse->OptionalResponse[1]; 927 } 928 else if ('UNSEEN' === $oImapResponse->OptionalResponse[0] && 929 isset($oImapResponse->OptionalResponse[1]) && 930 is_numeric($oImapResponse->OptionalResponse[1])) 931 { 932 $oResult->Unread = (int) $oImapResponse->OptionalResponse[1]; 933 } 934 else if ('UIDNEXT' === $oImapResponse->OptionalResponse[0] && 935 isset($oImapResponse->OptionalResponse[1])) 936 { 937 $oResult->Uidnext = $oImapResponse->OptionalResponse[1]; 938 } 939 else if ('HIGHESTMODSEQ' === $oImapResponse->OptionalResponse[0] && 940 isset($oImapResponse->OptionalResponse[1]) && 941 \is_numeric($oImapResponse->OptionalResponse[1])) 942 { 943 $oResult->HighestModSeq = \trim($oImapResponse->OptionalResponse[1]); 944 } 945 } 946 947 if (\count($oImapResponse->ResponseList) > 2 && 948 \is_string($oImapResponse->ResponseList[2]) && 949 \is_numeric($oImapResponse->ResponseList[1])) 950 { 951 switch($oImapResponse->ResponseList[2]) 952 { 953 case 'EXISTS': 954 $oResult->Exists = (int) $oImapResponse->ResponseList[1]; 955 break; 956 case 'RECENT': 957 $oResult->Recent = (int) $oImapResponse->ResponseList[1]; 958 break; 959 } 960 } 961 } 962 } 963 964 $this->oCurrentFolderInfo = $oResult; 965 } 966 } 967 968 /** 969 * @param string $sFolderName 970 * @param bool $bIsWritable 971 * @param bool $bReSelectSameFolders 972 * 973 * @return \MailSo\Imap\ImapClient 974 * 975 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 976 * @throws \MailSo\Net\Exceptions\Exception 977 * @throws \MailSo\Imap\Exceptions\Exception 978 */ 979 protected function selectOrExamineFolder($sFolderName, $bIsWritable, $bReSelectSameFolders) 980 { 981 if (!$bReSelectSameFolders) 982 { 983 if ($this->oCurrentFolderInfo && 984 $sFolderName === $this->oCurrentFolderInfo->FolderName && 985 $bIsWritable === $this->oCurrentFolderInfo->IsWritable) 986 { 987 return $this; 988 } 989 } 990 991 if (!\MailSo\Base\Validator::NotEmptyString($sFolderName, true)) 992 { 993 throw new \MailSo\Base\Exceptions\InvalidArgumentException(); 994 } 995 996 $this->SendRequest(($bIsWritable) ? 'SELECT' : 'EXAMINE', 997 array($this->EscapeString($sFolderName))); 998 999 $this->initCurrentFolderInformation( 1000 $this->parseResponseWithValidation(), $sFolderName, $bIsWritable); 1001 1002 $this->bIsSelected = true; 1003 1004 return $this; 1005 } 1006 1007 /** 1008 * @param string $sFolderName 1009 * @param bool $bReSelectSameFolders = false 1010 * 1011 * @return \MailSo\Imap\ImapClient 1012 * 1013 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1014 * @throws \MailSo\Net\Exceptions\Exception 1015 * @throws \MailSo\Imap\Exceptions\Exception 1016 */ 1017 public function FolderSelect($sFolderName, $bReSelectSameFolders = false) 1018 { 1019 return $this->selectOrExamineFolder($sFolderName, true, $bReSelectSameFolders); 1020 } 1021 1022 /** 1023 * @param string $sFolderName 1024 * @param bool $bReSelectSameFolders = false 1025 * 1026 * @return \MailSo\Imap\ImapClient 1027 * 1028 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1029 * @throws \MailSo\Net\Exceptions\Exception 1030 * @throws \MailSo\Imap\Exceptions\Exception 1031 */ 1032 public function FolderExamine($sFolderName, $bReSelectSameFolders = false) 1033 { 1034 return $this->selectOrExamineFolder($sFolderName, $this->__FORCE_SELECT_ON_EXAMINE__, $bReSelectSameFolders); 1035 } 1036 1037 /** 1038 * @return \MailSo\Imap\ImapClient 1039 * 1040 * @throws \MailSo\Net\Exceptions\Exception 1041 * @throws \MailSo\Imap\Exceptions\Exception 1042 */ 1043 public function FolderUnSelect() 1044 { 1045 if ($this->IsSelected() && $this->IsSupported('UNSELECT')) 1046 { 1047 $this->SendRequestWithCheck('UNSELECT'); 1048 $this->bIsSelected = false; 1049 } 1050 1051 return $this; 1052 } 1053 1054 /** 1055 * @param array $aInputFetchItems 1056 * @param string $sIndexRange 1057 * @param bool $bIndexIsUid 1058 * 1059 * @return array 1060 * 1061 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1062 * @throws \MailSo\Net\Exceptions\Exception 1063 * @throws \MailSo\Imap\Exceptions\Exception 1064 */ 1065 public function Fetch(array $aInputFetchItems, $sIndexRange, $bIndexIsUid) 1066 { 1067 $sIndexRange = (string) $sIndexRange; 1068 if (!\MailSo\Base\Validator::NotEmptyString($sIndexRange, true)) 1069 { 1070 $this->writeLogException( 1071 new \MailSo\Base\Exceptions\InvalidArgumentException(), 1072 \MailSo\Log\Enumerations\Type::ERROR, true); 1073 } 1074 1075 $aFetchItems = \MailSo\Imap\Enumerations\FetchType::ChangeFetchItemsBefourRequest($aInputFetchItems); 1076 foreach ($aFetchItems as $sName => $mItem) 1077 { 1078 if (0 < \strlen($sName) && '' !== $mItem) 1079 { 1080 if (null === $this->aFetchCallbacks) 1081 { 1082 $this->aFetchCallbacks = array(); 1083 } 1084 1085 $this->aFetchCallbacks[$sName] = $mItem; 1086 } 1087 } 1088 1089 $this->SendRequest((($bIndexIsUid) ? 'UID ' : '').'FETCH', array($sIndexRange, \array_keys($aFetchItems))); 1090 $aResult = $this->validateResponse($this->parseResponse()); 1091 $this->aFetchCallbacks = null; 1092 1093 $aReturn = array(); 1094 $oImapResponse = null; 1095 foreach ($aResult as $oImapResponse) 1096 { 1097 if (FetchResponse::IsValidFetchImapResponse($oImapResponse)) 1098 { 1099 if (FetchResponse::IsNotEmptyFetchImapResponse($oImapResponse)) 1100 { 1101 $aReturn[] = FetchResponse::NewInstance($oImapResponse); 1102 } 1103 else 1104 { 1105 if ($this->oLogger) 1106 { 1107 $this->oLogger->Write('Skipped Imap Response! ['.$oImapResponse->ToLine().']', \MailSo\Log\Enumerations\Type::NOTICE); 1108 } 1109 } 1110 } 1111 } 1112 1113 return $aReturn; 1114 } 1115 1116 1117 /** 1118 * @return array|false 1119 * 1120 * @throws \MailSo\Net\Exceptions\Exception 1121 * @throws \MailSo\Imap\Exceptions\Exception 1122 */ 1123 public function Quota() 1124 { 1125 $aReturn = false; 1126 if ($this->IsSupported('QUOTA')) 1127 { 1128 $this->SendRequest('GETQUOTAROOT "INBOX"'); 1129 $aResult = $this->parseResponseWithValidation(); 1130 1131 $aReturn = array(0, 0); 1132 $oImapResponse = null; 1133 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 1134 { 1135 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType 1136 && 'QUOTA' === $oImapResponse->StatusOrIndex 1137 && \is_array($oImapResponse->ResponseList) 1138 && isset($oImapResponse->ResponseList[3]) 1139 && \is_array($oImapResponse->ResponseList[3]) 1140 && 2 < \count($oImapResponse->ResponseList[3]) 1141 && 'STORAGE' === \strtoupper($oImapResponse->ResponseList[3][0]) 1142 && \is_numeric($oImapResponse->ResponseList[3][1]) 1143 && \is_numeric($oImapResponse->ResponseList[3][2]) 1144 ) 1145 { 1146 $aReturn = array( 1147 (int) $oImapResponse->ResponseList[3][1], 1148 (int) $oImapResponse->ResponseList[3][2], 1149 0, 1150 0 1151 ); 1152 1153 if (5 < \count($oImapResponse->ResponseList[3]) 1154 && 'MESSAGE' === \strtoupper($oImapResponse->ResponseList[3][3]) 1155 && \is_numeric($oImapResponse->ResponseList[3][4]) 1156 && \is_numeric($oImapResponse->ResponseList[3][5]) 1157 ) 1158 { 1159 $aReturn[2] = (int) $oImapResponse->ResponseList[3][4]; 1160 $aReturn[3] = (int) $oImapResponse->ResponseList[3][5]; 1161 } 1162 } 1163 } 1164 } 1165 1166 return $aReturn; 1167 } 1168 1169 /** 1170 * @param array $aSortTypes 1171 * @param string $sSearchCriterias 1172 * @param bool $bReturnUid 1173 * 1174 * @return array 1175 * 1176 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1177 * @throws \MailSo\Net\Exceptions\Exception 1178 * @throws \MailSo\Imap\Exceptions\Exception 1179 */ 1180 public function MessageSimpleSort($aSortTypes, $sSearchCriterias = 'ALL', $bReturnUid = true) 1181 { 1182 $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; 1183 $sSearchCriterias = !\MailSo\Base\Validator::NotEmptyString($sSearchCriterias, true) || '*' === $sSearchCriterias 1184 ? 'ALL' : $sSearchCriterias; 1185 1186 if (!\is_array($aSortTypes) || 0 === \count($aSortTypes)) 1187 { 1188 $this->writeLogException( 1189 new \MailSo\Base\Exceptions\InvalidArgumentException(), 1190 \MailSo\Log\Enumerations\Type::ERROR, true); 1191 } 1192 else if (!$this->IsSupported('SORT')) 1193 { 1194 $this->writeLogException( 1195 new \MailSo\Base\Exceptions\InvalidArgumentException(), 1196 \MailSo\Log\Enumerations\Type::ERROR, true); 1197 } 1198 1199 $aRequest = array(); 1200 $aRequest[] = $aSortTypes; 1201 $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; 1202 $aRequest[] = $sSearchCriterias; 1203 1204 $sCmd = 'SORT'; 1205 1206 $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); 1207 $aResult = $this->parseResponseWithValidation(); 1208 1209 $aReturn = array(); 1210 $oImapResponse = null; 1211 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 1212 { 1213 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType 1214 && ($sCmd === $oImapResponse->StatusOrIndex || 1215 ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && 1216 $sCmd === $oImapResponse->ResponseList[2]) 1217 && \is_array($oImapResponse->ResponseList) 1218 && 2 < \count($oImapResponse->ResponseList)) 1219 { 1220 $iStart = 2; 1221 if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && 1222 !empty($oImapResponse->ResponseList[2]) && 1223 $sCmd === $oImapResponse->ResponseList[2]) 1224 { 1225 $iStart = 3; 1226 } 1227 1228 for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) 1229 { 1230 $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; 1231 } 1232 } 1233 } 1234 1235 return $aReturn; 1236 } 1237 1238 /** 1239 * @param bool $bSort = false 1240 * @param string $sSearchCriterias = 'ALL' 1241 * @param array $aSearchOrSortReturn = null 1242 * @param bool $bReturnUid = true 1243 * @param string $sLimit = '' 1244 * @param string $sCharset = '' 1245 * @param array $aSortTypes = null 1246 * 1247 * @return array 1248 * 1249 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1250 * @throws \MailSo\Net\Exceptions\Exception 1251 * @throws \MailSo\Imap\Exceptions\Exception 1252 */ 1253 private function simpleESearchOrESortHelper($bSort = false, $sSearchCriterias = 'ALL', $aSearchOrSortReturn = null, $bReturnUid = true, $sLimit = '', $sCharset = '', $aSortTypes = null) 1254 { 1255 $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; 1256 $sSearchCriterias = 0 === \strlen($sSearchCriterias) || '*' === $sSearchCriterias 1257 ? 'ALL' : $sSearchCriterias; 1258 1259 $sCmd = $bSort ? 'SORT': 'SEARCH'; 1260 if ($bSort && (!\is_array($aSortTypes) || 0 === \count($aSortTypes) || !$this->IsSupported('SORT'))) 1261 { 1262 $this->writeLogException( 1263 new \MailSo\Base\Exceptions\InvalidArgumentException(), 1264 \MailSo\Log\Enumerations\Type::ERROR, true); 1265 } 1266 1267 if (!$this->IsSupported($bSort ? 'ESORT' : 'ESEARCH')) 1268 { 1269 $this->writeLogException( 1270 new \MailSo\Base\Exceptions\InvalidArgumentException(), 1271 \MailSo\Log\Enumerations\Type::ERROR, true); 1272 } 1273 1274 if (!\is_array($aSearchOrSortReturn) || 0 === \count($aSearchOrSortReturn)) 1275 { 1276 $aSearchOrSortReturn = array('ALL'); 1277 } 1278 1279 $aRequest = array(); 1280 if ($bSort) 1281 { 1282 $aRequest[] = 'RETURN'; 1283 $aRequest[] = $aSearchOrSortReturn; 1284 1285 $aRequest[] = $aSortTypes; 1286 $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; 1287 } 1288 else 1289 { 1290 if (0 < \strlen($sCharset)) 1291 { 1292 $aRequest[] = 'CHARSET'; 1293 $aRequest[] = \strtoupper($sCharset); 1294 } 1295 1296 $aRequest[] = 'RETURN'; 1297 $aRequest[] = $aSearchOrSortReturn; 1298 } 1299 1300 $aRequest[] = $sSearchCriterias; 1301 1302 if (0 < \strlen($sLimit)) 1303 { 1304 $aRequest[] = $sLimit; 1305 } 1306 1307 $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); 1308 $sRequestTag = $this->getCurrentTag(); 1309 1310 $aResult = array(); 1311 $aResponse = $this->parseResponseWithValidation(); 1312 1313 if (\is_array($aResponse)) 1314 { 1315 $oImapResponse = null; 1316 foreach ($aResponse as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 1317 { 1318 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType 1319 && ('ESEARCH' === $oImapResponse->StatusOrIndex || 'ESORT' === $oImapResponse->StatusOrIndex) 1320 && \is_array($oImapResponse->ResponseList) 1321 && isset($oImapResponse->ResponseList[2], $oImapResponse->ResponseList[2][0], $oImapResponse->ResponseList[2][1]) 1322 && 'TAG' === $oImapResponse->ResponseList[2][0] && $sRequestTag === $oImapResponse->ResponseList[2][1] 1323 && (!$bReturnUid || ($bReturnUid && !empty($oImapResponse->ResponseList[3]) && 'UID' === $oImapResponse->ResponseList[3])) 1324 ) 1325 { 1326 $iStart = 3; 1327 foreach ($oImapResponse->ResponseList as $iIndex => $mItem) 1328 { 1329 if ($iIndex >= $iStart) 1330 { 1331 switch ($mItem) 1332 { 1333 case 'ALL': 1334 case 'MAX': 1335 case 'MIN': 1336 case 'COUNT': 1337 if (isset($oImapResponse->ResponseList[$iIndex + 1])) 1338 { 1339 $aResult[$mItem] = $oImapResponse->ResponseList[$iIndex + 1]; 1340 } 1341 break; 1342 } 1343 } 1344 } 1345 } 1346 } 1347 } 1348 1349 return $aResult; 1350 } 1351 1352 /** 1353 * @param string $sSearchCriterias = 'ALL' 1354 * @param array $aSearchReturn = null 1355 * @param bool $bReturnUid = true 1356 * @param string $sLimit = '' 1357 * @param string $sCharset = '' 1358 * 1359 * @return array 1360 * 1361 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1362 * @throws \MailSo\Net\Exceptions\Exception 1363 * @throws \MailSo\Imap\Exceptions\Exception 1364 */ 1365 public function MessageSimpleESearch($sSearchCriterias = 'ALL', $aSearchReturn = null, $bReturnUid = true, $sLimit = '', $sCharset = '') 1366 { 1367 return $this->simpleESearchOrESortHelper(false, $sSearchCriterias, $aSearchReturn, $bReturnUid, $sLimit, $sCharset); 1368 } 1369 1370 /** 1371 * @param array $aSortTypes 1372 * @param string $sSearchCriterias = 'ALL' 1373 * @param array $aSearchReturn = null 1374 * @param bool $bReturnUid = true 1375 * @param string $sLimit = '' 1376 * 1377 * @return array 1378 * 1379 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1380 * @throws \MailSo\Net\Exceptions\Exception 1381 * @throws \MailSo\Imap\Exceptions\Exception 1382 */ 1383 public function MessageSimpleESort($aSortTypes, $sSearchCriterias = 'ALL', $aSearchReturn = null, $bReturnUid = true, $sLimit = '') 1384 { 1385 return $this->simpleESearchOrESortHelper(true, $sSearchCriterias, $aSearchReturn, $bReturnUid, $sLimit, '', $aSortTypes); 1386 } 1387 1388 /** 1389 * @param array $aResult 1390 * @return \MailSo\Imap\Response 1391 */ 1392 private function findLastResponse($aResult) 1393 { 1394 $oResult = null; 1395 if (\is_array($aResult) && 0 < \count($aResult)) 1396 { 1397 $oResult = $aResult[\count($aResult) - 1]; 1398 if (!($oResult instanceof \MailSo\Imap\Response)) 1399 { 1400 $oResult = null; 1401 } 1402 } 1403 1404 return $oResult; 1405 } 1406 1407 /** 1408 * @param string $sSearchCriterias 1409 * @param bool $bReturnUid = true 1410 * @param string $sCharset = '' 1411 * 1412 * @return array 1413 * 1414 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1415 * @throws \MailSo\Net\Exceptions\Exception 1416 * @throws \MailSo\Imap\Exceptions\Exception 1417 */ 1418 public function MessageSimpleSearch($sSearchCriterias = 'ALL', $bReturnUid = true, $sCharset = '') 1419 { 1420 $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; 1421 $sSearchCriterias = 0 === \strlen($sSearchCriterias) || '*' === $sSearchCriterias 1422 ? 'ALL' : $sSearchCriterias; 1423 1424 $aRequest = array(); 1425 if (0 < \strlen($sCharset)) 1426 { 1427 $aRequest[] = 'CHARSET'; 1428 $aRequest[] = \strtoupper($sCharset); 1429 } 1430 1431 $aRequest[] = $sSearchCriterias; 1432 1433 $sCmd = 'SEARCH'; 1434 1435 $sCont = $this->SendRequest($sCommandPrefix.$sCmd, $aRequest, true); 1436 if ('' !== $sCont) 1437 { 1438 $aResult = $this->parseResponseWithValidation(); 1439 $oItem = $this->findLastResponse($aResult); 1440 1441 if ($oItem && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oItem->ResponseType) 1442 { 1443 $aParts = explode("\r\n", $sCont); 1444 foreach ($aParts as $sLine) 1445 { 1446 $this->sendRaw($sLine); 1447 1448 $aResult = $this->parseResponseWithValidation(); 1449 $oItem = $this->findLastResponse($aResult); 1450 if ($oItem && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oItem->ResponseType) 1451 { 1452 continue; 1453 } 1454 } 1455 } 1456 } 1457 else 1458 { 1459 $aResult = $this->parseResponseWithValidation(); 1460 } 1461 1462 $aReturn = array(); 1463 $oImapResponse = null; 1464 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 1465 { 1466 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType 1467 && ($sCmd === $oImapResponse->StatusOrIndex || 1468 ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && 1469 $sCmd === $oImapResponse->ResponseList[2]) 1470 && \is_array($oImapResponse->ResponseList) 1471 && 2 < count($oImapResponse->ResponseList)) 1472 { 1473 $iStart = 2; 1474 if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && 1475 !empty($oImapResponse->ResponseList[2]) && 1476 $sCmd === $oImapResponse->ResponseList[2]) 1477 { 1478 $iStart = 3; 1479 } 1480 1481 for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) 1482 { 1483 $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; 1484 } 1485 } 1486 } 1487 1488 $aReturn = \array_reverse($aReturn); 1489 return $aReturn; 1490 } 1491 1492 /** 1493 * @param mixed $aValue 1494 * 1495 * @return mixed 1496 */ 1497 private function validateThreadItem($aValue) 1498 { 1499 $mResult = false; 1500 if (\is_numeric($aValue)) 1501 { 1502 $mResult = (int) $aValue; 1503 if (0 >= $mResult) 1504 { 1505 $mResult = false; 1506 } 1507 } 1508 else if (\is_array($aValue)) 1509 { 1510 if (1 === \count($aValue) && \is_numeric($aValue[0])) 1511 { 1512 $mResult = (int) $aValue[0]; 1513 if (0 >= $mResult) 1514 { 1515 $mResult = false; 1516 } 1517 } 1518 else 1519 { 1520 $mResult = array(); 1521 foreach ($aValue as $aValueItem) 1522 { 1523 $mTemp = $this->validateThreadItem($aValueItem); 1524 if (false !== $mTemp) 1525 { 1526 $mResult[] = $mTemp; 1527 } 1528 } 1529 } 1530 } 1531 1532 return $mResult; 1533 } 1534 1535 /** 1536 * @param string $sSearchCriterias = 'ALL' 1537 * @param bool $bReturnUid = true 1538 * @param string $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8 1539 * 1540 * @return array 1541 * 1542 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1543 * @throws \MailSo\Net\Exceptions\Exception 1544 * @throws \MailSo\Imap\Exceptions\Exception 1545 */ 1546 public function MessageSimpleThread($sSearchCriterias = 'ALL', $bReturnUid = true, $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8) 1547 { 1548 $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; 1549 $sSearchCriterias = !\MailSo\Base\Validator::NotEmptyString($sSearchCriterias, true) || '*' === $sSearchCriterias 1550 ? 'ALL' : $sSearchCriterias; 1551 1552 $sThreadType = ''; 1553 switch (true) 1554 { 1555 case $this->IsSupported('THREAD=REFS'): 1556 $sThreadType = 'REFS'; 1557 break; 1558 case $this->IsSupported('THREAD=REFERENCES'): 1559 $sThreadType = 'REFERENCES'; 1560 break; 1561 case $this->IsSupported('THREAD=ORDEREDSUBJECT'): 1562 $sThreadType = 'ORDEREDSUBJECT'; 1563 break; 1564 default: 1565 $this->writeLogException( 1566 new Exceptions\RuntimeException('Thread is not supported'), 1567 \MailSo\Log\Enumerations\Type::ERROR, true); 1568 break; 1569 } 1570 1571 $aRequest = array(); 1572 $aRequest[] = $sThreadType; 1573 $aRequest[] = \strtoupper($sCharset); 1574 $aRequest[] = $sSearchCriterias; 1575 1576 $sCmd = 'THREAD'; 1577 1578 $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); 1579 $aResult = $this->parseResponseWithValidation(); 1580 1581 $aReturn = array(); 1582 $oImapResponse = null; 1583 1584 foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) 1585 { 1586 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType 1587 && ($sCmd === $oImapResponse->StatusOrIndex || 1588 ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && 1589 $sCmd === $oImapResponse->ResponseList[2]) 1590 && \is_array($oImapResponse->ResponseList) 1591 && 2 < \count($oImapResponse->ResponseList)) 1592 { 1593 $iStart = 2; 1594 if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && 1595 !empty($oImapResponse->ResponseList[2]) && 1596 $sCmd === $oImapResponse->ResponseList[2]) 1597 { 1598 $iStart = 3; 1599 } 1600 1601 for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) 1602 { 1603 $aNewValue = $this->validateThreadItem($oImapResponse->ResponseList[$iIndex]); 1604 if (false !== $aNewValue) 1605 { 1606 $aReturn[] = $aNewValue; 1607 } 1608 } 1609 } 1610 } 1611 1612 return $aReturn; 1613 } 1614 1615 /** 1616 * @param string $sToFolder 1617 * @param string $sIndexRange 1618 * @param bool $bIndexIsUid 1619 * 1620 * @return \MailSo\Imap\ImapClient 1621 * 1622 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1623 * @throws \MailSo\Net\Exceptions\Exception 1624 * @throws \MailSo\Imap\Exceptions\Exception 1625 */ 1626 public function MessageCopy($sToFolder, $sIndexRange, $bIndexIsUid) 1627 { 1628 if (0 === \strlen($sIndexRange)) 1629 { 1630 $this->writeLogException( 1631 new \MailSo\Base\Exceptions\InvalidArgumentException(), 1632 \MailSo\Log\Enumerations\Type::ERROR, true); 1633 } 1634 1635 $sCommandPrefix = ($bIndexIsUid) ? 'UID ' : ''; 1636 return $this->SendRequestWithCheck($sCommandPrefix.'COPY', 1637 array($sIndexRange, $this->EscapeString($sToFolder))); 1638 } 1639 1640 /** 1641 * @param string $sToFolder 1642 * @param string $sIndexRange 1643 * @param bool $bIndexIsUid 1644 * 1645 * @return \MailSo\Imap\ImapClient 1646 * 1647 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1648 * @throws \MailSo\Net\Exceptions\Exception 1649 * @throws \MailSo\Imap\Exceptions\Exception 1650 */ 1651 public function MessageMove($sToFolder, $sIndexRange, $bIndexIsUid) 1652 { 1653 if (0 === \strlen($sIndexRange)) 1654 { 1655 $this->writeLogException( 1656 new \MailSo\Base\Exceptions\InvalidArgumentException(), 1657 \MailSo\Log\Enumerations\Type::ERROR, true); 1658 } 1659 1660 if (!$this->IsSupported('MOVE')) 1661 { 1662 $this->writeLogException( 1663 new Exceptions\RuntimeException('Move is not supported'), 1664 \MailSo\Log\Enumerations\Type::ERROR, true); 1665 } 1666 1667 $sCommandPrefix = ($bIndexIsUid) ? 'UID ' : ''; 1668 return $this->SendRequestWithCheck($sCommandPrefix.'MOVE', 1669 array($sIndexRange, $this->EscapeString($sToFolder))); 1670 } 1671 1672 /** 1673 * @param string $sUidRangeIfSupported = '' 1674 * @param bool $bForceUidExpunge = false 1675 * @param bool $bExpungeAll = false 1676 * 1677 * @return \MailSo\Imap\ImapClient 1678 * 1679 * @throws \MailSo\Net\Exceptions\Exception 1680 * @throws \MailSo\Imap\Exceptions\Exception 1681 */ 1682 public function MessageExpunge($sUidRangeIfSupported = '', $bForceUidExpunge = false, $bExpungeAll = false) 1683 { 1684 $sUidRangeIfSupported = \trim($sUidRangeIfSupported); 1685 1686 $sCmd = 'EXPUNGE'; 1687 $aArguments = array(); 1688 1689 if (!$bExpungeAll && $bForceUidExpunge && 0 < \strlen($sUidRangeIfSupported) && $this->IsSupported('UIDPLUS')) 1690 { 1691 $sCmd = 'UID '.$sCmd; 1692 $aArguments = array($sUidRangeIfSupported); 1693 } 1694 1695 return $this->SendRequestWithCheck($sCmd, $aArguments); 1696 } 1697 1698 /** 1699 * @param string $sIndexRange 1700 * @param bool $bIndexIsUid 1701 * @param array $aInputStoreItems 1702 * @param string $sStoreAction 1703 * 1704 * @return \MailSo\Imap\ImapClient 1705 * 1706 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1707 * @throws \MailSo\Net\Exceptions\Exception 1708 * @throws \MailSo\Imap\Exceptions\Exception 1709 */ 1710 public function MessageStoreFlag($sIndexRange, $bIndexIsUid, $aInputStoreItems, $sStoreAction) 1711 { 1712 if (!\MailSo\Base\Validator::NotEmptyString($sIndexRange, true) || 1713 !\MailSo\Base\Validator::NotEmptyString($sStoreAction, true) || 1714 0 === \count($aInputStoreItems)) 1715 { 1716 return false; 1717 } 1718 1719 $sCmd = ($bIndexIsUid) ? 'UID STORE' : 'STORE'; 1720 return $this->SendRequestWithCheck($sCmd, array($sIndexRange, $sStoreAction, $aInputStoreItems)); 1721 } 1722 1723 /** 1724 * @param string $sFolderName 1725 * @param resource $rMessageAppendStream 1726 * @param int $iStreamSize 1727 * @param array $aAppendFlags = null 1728 * @param int $iUid = null 1729 * @param int $sDateTime = 0 1730 * 1731 * @return \MailSo\Imap\ImapClient 1732 * 1733 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1734 * @throws \MailSo\Net\Exceptions\Exception 1735 * @throws \MailSo\Imap\Exceptions\Exception 1736 */ 1737 public function MessageAppendStream($sFolderName, $rMessageAppendStream, $iStreamSize, $aAppendFlags = null, &$iUid = null, $sDateTime = 0) 1738 { 1739 $aData = array($this->EscapeString($sFolderName), $aAppendFlags); 1740 if (0 < $sDateTime) 1741 { 1742 $aData[] = $this->EscapeString(\gmdate('d-M-Y H:i:s', $sDateTime).' +0000'); 1743 } 1744 1745 $aData[] = '{'.$iStreamSize.'}'; 1746 1747 $this->SendRequest('APPEND', $aData); 1748 $this->parseResponseWithValidation(); 1749 1750 $this->writeLog('Write to connection stream', \MailSo\Log\Enumerations\Type::NOTE); 1751 1752 \MailSo\Base\Utils::MultipleStreamWriter($rMessageAppendStream, array($this->rConnect)); 1753 1754 $this->sendRaw(''); 1755 $this->parseResponseWithValidation(); 1756 1757 if (null !== $iUid) 1758 { 1759 $aLastResponse = $this->GetLastResponse(); 1760 if (\is_array($aLastResponse) && 0 < \count($aLastResponse) && $aLastResponse[\count($aLastResponse) - 1]) 1761 { 1762 $oLast = $aLastResponse[count($aLastResponse) - 1]; 1763 if ($oLast && \MailSo\Imap\Enumerations\ResponseType::TAGGED === $oLast->ResponseType && \is_array($oLast->OptionalResponse)) 1764 { 1765 if (0 < \strlen($oLast->OptionalResponse[0]) && 1766 0 < \strlen($oLast->OptionalResponse[2]) && 1767 'APPENDUID' === strtoupper($oLast->OptionalResponse[0]) && 1768 \is_numeric($oLast->OptionalResponse[2]) 1769 ) 1770 { 1771 $iUid = (int) $oLast->OptionalResponse[2]; 1772 } 1773 } 1774 } 1775 } 1776 1777 return $this; 1778 } 1779 1780 /** 1781 * @return \MailSo\Imap\FolderInformation 1782 */ 1783 public function FolderCurrentInformation() 1784 { 1785 return $this->oCurrentFolderInfo; 1786 } 1787 1788 /** 1789 * @param string $sCommand 1790 * @param array $aParams = array() 1791 * @param bool $bBreakOnLiteral = false 1792 * 1793 * @return string 1794 * 1795 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1796 * @throws \MailSo\Net\Exceptions\Exception 1797 */ 1798 public function SendRequest($sCommand, $aParams = array(), $bBreakOnLiteral = false) 1799 { 1800 if (!\MailSo\Base\Validator::NotEmptyString($sCommand, true) || !\is_array($aParams)) 1801 { 1802 $this->writeLogException( 1803 new \MailSo\Base\Exceptions\InvalidArgumentException(), 1804 \MailSo\Log\Enumerations\Type::ERROR, true); 1805 } 1806 1807 $this->IsConnected(true); 1808 1809 $sTag = $this->getNewTag(); 1810 1811 $sCommand = \trim($sCommand); 1812 $sRealCommand = $sTag.' '.$sCommand.$this->prepearParamLine($aParams); 1813 1814 $sFakeCommand = ''; 1815 $aFakeParams = $this->secureRequestParams($sCommand, $aParams); 1816 if (null !== $aFakeParams) 1817 { 1818 $sFakeCommand = $sTag.' '.$sCommand.$this->prepearParamLine($aFakeParams); 1819 } 1820 1821 $this->aTagTimeouts[$sTag] = \microtime(true); 1822 1823 if ($bBreakOnLiteral && !\preg_match('/\d\+\}\r\n/', $sRealCommand)) 1824 { 1825 $iPos = \strpos($sRealCommand, "}\r\n"); 1826 if (false !== $iPos) 1827 { 1828 $iFakePos = \strpos($sFakeCommand, "}\r\n"); 1829 1830 $this->sendRaw(\substr($sRealCommand, 0, $iPos + 1), true, 1831 false !== $iFakePos ? \substr($sFakeCommand, 0, $iFakePos + 3) : ''); 1832 1833 return \substr($sRealCommand, $iPos + 3); 1834 } 1835 } 1836 1837 $this->sendRaw($sRealCommand, true, $sFakeCommand); 1838 return ''; 1839 } 1840 1841 /** 1842 * @param string $sCommand 1843 * @param array $aParams 1844 * 1845 * @return array|null 1846 */ 1847 private function secureRequestParams($sCommand, $aParams) 1848 { 1849 $aResult = null; 1850 switch ($sCommand) 1851 { 1852 case 'LOGIN': 1853 $aResult = $aParams; 1854 if (\is_array($aResult) && 2 === count($aResult)) 1855 { 1856 $aResult[1] = '"********"'; 1857 } 1858 break; 1859 } 1860 1861 return $aResult; 1862 } 1863 1864 /** 1865 * @param string $sCommand 1866 * @param array $aParams = array() 1867 * @param bool $bFindCapa = false 1868 * 1869 * @return \MailSo\Imap\ImapClient 1870 * 1871 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 1872 * @throws \MailSo\Net\Exceptions\Exception 1873 * @throws \MailSo\Imap\Exceptions\Exception 1874 */ 1875 public function SendRequestWithCheck($sCommand, $aParams = array(), $bFindCapa = false) 1876 { 1877 $this->SendRequest($sCommand, $aParams); 1878 $this->parseResponseWithValidation(null, $bFindCapa); 1879 1880 return $this; 1881 } 1882 1883 /** 1884 * @return array 1885 */ 1886 public function GetLastResponse() 1887 { 1888 return $this->aLastResponse; 1889 } 1890 1891 /** 1892 * @param mixed $aResult 1893 * 1894 * @return array 1895 * 1896 * @throws \MailSo\Imap\Exceptions\ResponseNotFoundException 1897 * @throws \MailSo\Imap\Exceptions\InvalidResponseException 1898 * @throws \MailSo\Imap\Exceptions\NegativeResponseException 1899 */ 1900 private function validateResponse($aResult) 1901 { 1902 if (!\is_array($aResult) || 0 === $iCnt = \count($aResult)) 1903 { 1904 $this->writeLogException( 1905 new Exceptions\ResponseNotFoundException(), 1906 \MailSo\Log\Enumerations\Type::WARNING, true); 1907 } 1908 1909 if ($aResult[$iCnt - 1]->ResponseType !== \MailSo\Imap\Enumerations\ResponseType::CONTINUATION) 1910 { 1911 if (!$aResult[$iCnt - 1]->IsStatusResponse) 1912 { 1913 $this->writeLogException( 1914 new Exceptions\InvalidResponseException($aResult), 1915 \MailSo\Log\Enumerations\Type::WARNING, true); 1916 } 1917 1918 if (\MailSo\Imap\Enumerations\ResponseStatus::OK !== $aResult[$iCnt - 1]->StatusOrIndex) 1919 { 1920 $this->writeLogException( 1921 new Exceptions\NegativeResponseException($aResult), 1922 \MailSo\Log\Enumerations\Type::WARNING, true); 1923 } 1924 } 1925 1926 return $aResult; 1927 } 1928 1929 /** 1930 * @param string $sEndTag = null 1931 * @param bool $bFindCapa = false 1932 * 1933 * @return array|bool 1934 */ 1935 protected function parseResponse($sEndTag = null, $bFindCapa = false) 1936 { 1937 if (\is_resource($this->rConnect)) 1938 { 1939 $oImapResponse = null; 1940 $sEndTag = (null === $sEndTag) ? $this->getCurrentTag() : $sEndTag; 1941 1942 while (true) 1943 { 1944 $oImapResponse = Response::NewInstance(); 1945 1946 $this->partialParseResponseBranch($oImapResponse); 1947 1948 if ($oImapResponse) 1949 { 1950 if (\MailSo\Imap\Enumerations\ResponseType::UNKNOWN === $oImapResponse->ResponseType) 1951 { 1952 return false; 1953 } 1954 1955 if ($bFindCapa) 1956 { 1957 $this->initCapabilityImapResponse($oImapResponse); 1958 } 1959 1960 $this->aPartialResponses[] = $oImapResponse; 1961 if ($sEndTag === $oImapResponse->Tag || \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oImapResponse->ResponseType) 1962 { 1963 if (isset($this->aTagTimeouts[$sEndTag])) 1964 { 1965 $this->writeLog((\microtime(true) - $this->aTagTimeouts[$sEndTag]).' ('.$sEndTag.')', 1966 \MailSo\Log\Enumerations\Type::TIME); 1967 1968 unset($this->aTagTimeouts[$sEndTag]); 1969 } 1970 1971 break; 1972 } 1973 } 1974 else 1975 { 1976 return false; 1977 } 1978 1979 unset($oImapResponse); 1980 } 1981 } 1982 1983 $this->iResponseBufParsedPos = 0; 1984 $this->aLastResponse = $this->aPartialResponses; 1985 $this->aPartialResponses = array(); 1986 1987 return $this->aLastResponse; 1988 } 1989 1990 /** 1991 * @param string $sEndTag = null 1992 * @param bool $bFindCapa = false 1993 * 1994 * @return array 1995 */ 1996 private function parseResponseWithValidation($sEndTag = null, $bFindCapa = false) 1997 { 1998 return $this->validateResponse($this->parseResponse($sEndTag, $bFindCapa)); 1999 } 2000 2001 /** 2002 * @param \MailSo\Imap\Response $oImapResponse 2003 * 2004 * @return void 2005 */ 2006 private function initCapabilityImapResponse($oImapResponse) 2007 { 2008 if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType 2009 && \is_array($oImapResponse->ResponseList)) 2010 { 2011 $aList = null; 2012 if (isset($oImapResponse->ResponseList[1]) && \is_string($oImapResponse->ResponseList[1]) && 2013 'CAPABILITY' === \strtoupper($oImapResponse->ResponseList[1])) 2014 { 2015 $aList = \array_slice($oImapResponse->ResponseList, 2); 2016 } 2017 else if ($oImapResponse->OptionalResponse && \is_array($oImapResponse->OptionalResponse) && 2018 1 < \count($oImapResponse->OptionalResponse) && \is_string($oImapResponse->OptionalResponse[0]) && 2019 'CAPABILITY' === \strtoupper($oImapResponse->OptionalResponse[0])) 2020 { 2021 $aList = \array_slice($oImapResponse->OptionalResponse, 1); 2022 } 2023 2024 if (\is_array($aList) && 0 < \count($aList)) 2025 { 2026 $this->aCapabilityItems = \array_map('strtoupper', $aList); 2027 } 2028 } 2029 } 2030 2031 2032 2033 /** 2034 * @return bool 2035 */ 2036 private function skipBracketParse($oImapResponse) 2037 { 2038 return $oImapResponse && 2039 $oImapResponse->ResponseType === \MailSo\Imap\Enumerations\ResponseType::UNTAGGED && 2040 ( 2041 ($oImapResponse->StatusOrIndex === 'STATUS' && 2 === \count($oImapResponse->ResponseList)) || 2042 ($oImapResponse->StatusOrIndex === 'LIST' && 4 === \count($oImapResponse->ResponseList)) || 2043 ($oImapResponse->StatusOrIndex === 'LSUB' && 4 === \count($oImapResponse->ResponseList)) 2044 ); 2045 } 2046 2047 /** 2048 * @return array|string 2049 * 2050 * @throws \MailSo\Net\Exceptions\Exception 2051 */ 2052 private function partialParseResponseBranch(&$oImapResponse, 2053 $bTreatAsAtom = false, $sParentToken = '', $sOpenBracket = '') 2054 { 2055 $mNull = null; 2056 2057 $iPos = $this->iResponseBufParsedPos; 2058 $sClosingBracket = ''; 2059 2060 $sPreviousAtomUpperCase = null; 2061 $bIsEndOfList = false; 2062 $iLiteralLen = 0; 2063 $iBufferEndIndex = 0; 2064 $iDebugCount = 0; 2065 2066 $rImapLiteralStream = null; 2067 2068 $bIsGotoDefault = false; 2069 $bIsGotoLiteral = false; 2070 $bIsGotoLiteralEnd = false; 2071 $bIsGotoAtomBracket = false; 2072 $bIsGotoNotAtomBracket = false; 2073 2074 $bCountOneInited = false; 2075 $bCountTwoInited = false; 2076 2077 $sAtomBuilder = $bTreatAsAtom ? '' : null; 2078 $aList = array(); 2079 if (null !== $oImapResponse) 2080 { 2081 $aList =& $oImapResponse->ResponseList; 2082 } 2083 2084 while (!$bIsEndOfList) 2085 { 2086 $iDebugCount++; 2087 if (100000 === $iDebugCount) 2088 { 2089 $this->Logger()->Write('PartialParseOver: '.$iDebugCount, \MailSo\Log\Enumerations\Type::ERROR); 2090 } 2091 2092 if ($this->bNeedNext) 2093 { 2094 $iPos = 0; 2095 $this->getNextBuffer(); 2096 $this->iResponseBufParsedPos = $iPos; 2097 $this->bNeedNext = false; 2098 } 2099 2100 $sChar = null; 2101 if ($bIsGotoDefault) 2102 { 2103 $sChar = 'GOTO_DEFAULT'; 2104 $bIsGotoDefault = false; 2105 } 2106 else if ($bIsGotoLiteral) 2107 { 2108 $bIsGotoLiteral = false; 2109 $bIsGotoLiteralEnd = true; 2110 2111 if ($this->partialResponseLiteralCallbackCallable( 2112 $sParentToken, null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $this->rConnect, $iLiteralLen)) 2113 { 2114 if (!$bTreatAsAtom) 2115 { 2116 $aList[] = ''; 2117 } 2118 } 2119 else 2120 { 2121 $sLiteral = ''; 2122 $iRead = $iLiteralLen; 2123 2124 while (0 < $iRead) 2125 { 2126 $sAddRead = \fread($this->rConnect, $iRead); 2127 if (false === $sAddRead) 2128 { 2129 $sLiteral = false; 2130 break; 2131 } 2132 2133 $sLiteral .= $sAddRead; 2134 $iRead -= \strlen($sAddRead); 2135 2136 \MailSo\Base\Utils::ResetTimeLimit(); 2137 } 2138 2139 if (false !== $sLiteral) 2140 { 2141 $iLiteralSize = \strlen($sLiteral); 2142 \MailSo\Base\Loader::IncStatistic('NetRead', $iLiteralSize); 2143 if ($iLiteralLen !== $iLiteralSize) 2144 { 2145 $this->writeLog('Literal stream read warning "read '.$iLiteralSize.' of '. 2146 $iLiteralLen.'" bytes', \MailSo\Log\Enumerations\Type::WARNING); 2147 } 2148 2149 if (!$bTreatAsAtom) 2150 { 2151 $aList[] = $sLiteral; 2152 2153 if (\MailSo\Config::$LogSimpleLiterals) 2154 { 2155 $this->writeLog('{'.\strlen($sLiteral).'} '.$sLiteral, \MailSo\Log\Enumerations\Type::INFO); 2156 } 2157 } 2158 } 2159 else 2160 { 2161 $this->writeLog('Can\'t read imap stream', \MailSo\Log\Enumerations\Type::NOTE); 2162 } 2163 2164 unset($sLiteral); 2165 } 2166 2167 continue; 2168 } 2169 else if ($bIsGotoLiteralEnd) 2170 { 2171 $rImapLiteralStream = null; 2172 $sPreviousAtomUpperCase = null; 2173 $this->bNeedNext = true; 2174 $bIsGotoLiteralEnd = false; 2175 2176 continue; 2177 } 2178 else if ($bIsGotoAtomBracket) 2179 { 2180 if ($bTreatAsAtom) 2181 { 2182 $sAtomBlock = $this->partialParseResponseBranch($mNull, true, 2183 null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $sOpenBracket); 2184 2185 $sAtomBuilder .= $sAtomBlock; 2186 $iPos = $this->iResponseBufParsedPos; 2187 $sAtomBuilder .= $sClosingBracket; 2188 } 2189 2190 $sPreviousAtomUpperCase = null; 2191 $bIsGotoAtomBracket = false; 2192 2193 continue; 2194 } 2195 else if ($bIsGotoNotAtomBracket) 2196 { 2197 $aSubItems = $this->partialParseResponseBranch($mNull, false, 2198 null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $sOpenBracket); 2199 2200 $aList[] = $aSubItems; 2201 $iPos = $this->iResponseBufParsedPos; 2202 $sPreviousAtomUpperCase = null; 2203 if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) 2204 { 2205 $oImapResponse->OptionalResponse = $aSubItems; 2206 2207 $bIsGotoDefault = true; 2208 $bIsGotoNotAtomBracket = false; 2209 continue; 2210 } 2211 $bIsGotoNotAtomBracket = false; 2212 2213 continue; 2214 } 2215 else 2216 { 2217 $iBufferEndIndex = \strlen($this->sResponseBuffer) - 3; 2218 $this->bResponseBufferChanged = false; 2219 2220 if ($iPos > $iBufferEndIndex) 2221 { 2222 break; 2223 } 2224 2225 $sChar = $this->sResponseBuffer[$iPos]; 2226 } 2227 2228 switch (true) 2229 { 2230 case ']' === $sChar: 2231 case ')' === $sChar: 2232 if ($this->skipBracketParse($oImapResponse)) 2233 { 2234 $bIsGotoDefault = true; 2235 $bIsGotoNotAtomBracket = false; 2236 } 2237 else 2238 { 2239 $iPos++; 2240 $sPreviousAtomUpperCase = null; 2241 $bIsEndOfList = true; 2242 } 2243 break; 2244 case ' ' === $sChar: 2245 if ($bTreatAsAtom) 2246 { 2247 $sAtomBuilder .= ' '; 2248 } 2249 $iPos++; 2250 break; 2251 case '[' === $sChar: 2252 case '(' === $sChar: 2253 $sClosingBracket = '[' === $sChar ? ']' : ')'; 2254 $sOpenBracket = $sChar; 2255 2256 if ($bTreatAsAtom) 2257 { 2258 $sAtomBuilder .= $sChar; 2259 $bIsGotoAtomBracket = true; 2260 $this->iResponseBufParsedPos = ++$iPos; 2261 } 2262 else if ($this->skipBracketParse($oImapResponse)) 2263 { 2264 $sOpenBracket = ''; 2265 $sClosingBracket = ''; 2266 $bIsGotoDefault = true; 2267 $bIsGotoNotAtomBracket = false; 2268 } 2269 else 2270 { 2271 $bIsGotoNotAtomBracket = true; 2272 $this->iResponseBufParsedPos = ++$iPos; 2273 } 2274 break; 2275 case '{' === $sChar: 2276 $bIsLiteralParsed = false; 2277 $mLiteralEndPos = \strpos($this->sResponseBuffer, '}', $iPos); 2278 if (false !== $mLiteralEndPos && $mLiteralEndPos > $iPos) 2279 { 2280 $sLiteralLenAsString = \substr($this->sResponseBuffer, $iPos + 1, $mLiteralEndPos - $iPos - 1); 2281 if (\is_numeric($sLiteralLenAsString)) 2282 { 2283 $iLiteralLen = (int) $sLiteralLenAsString; 2284 $bIsLiteralParsed = true; 2285 $iPos = $mLiteralEndPos + 3; 2286 $bIsGotoLiteral = true; 2287 break; 2288 } 2289 } 2290 if (!$bIsLiteralParsed) 2291 { 2292 $iPos = $iBufferEndIndex; 2293 } 2294 $sPreviousAtomUpperCase = null; 2295 break; 2296 case '"' === $sChar: 2297 $bIsQuotedParsed = false; 2298 while (true) 2299 { 2300 $iClosingPos = $iPos + 1; 2301 if ($iClosingPos > $iBufferEndIndex) 2302 { 2303 break; 2304 } 2305 2306 while (true) 2307 { 2308 $iClosingPos = \strpos($this->sResponseBuffer, '"', $iClosingPos); 2309 if (false === $iClosingPos) 2310 { 2311 break; 2312 } 2313 2314 // TODO 2315 $iClosingPosNext = $iClosingPos + 1; 2316 if ( 2317 isset($this->sResponseBuffer[$iClosingPosNext]) && 2318 ' ' !== $this->sResponseBuffer[$iClosingPosNext] && 2319 "\r" !== $this->sResponseBuffer[$iClosingPosNext] && 2320 "\n" !== $this->sResponseBuffer[$iClosingPosNext] && 2321 ']' !== $this->sResponseBuffer[$iClosingPosNext] && 2322 ')' !== $this->sResponseBuffer[$iClosingPosNext] 2323 ) 2324 { 2325 $iClosingPos++; 2326 continue; 2327 } 2328 2329 $iSlashCount = 0; 2330 while ('\\' === $this->sResponseBuffer[$iClosingPos - $iSlashCount - 1]) 2331 { 2332 $iSlashCount++; 2333 } 2334 2335 if ($iSlashCount % 2 == 1) 2336 { 2337 $iClosingPos++; 2338 continue; 2339 } 2340 else 2341 { 2342 break; 2343 } 2344 } 2345 2346 if (false === $iClosingPos) 2347 { 2348 break; 2349 } 2350 else 2351 { 2352// $iSkipClosingPos = 0; 2353 $bIsQuotedParsed = true; 2354 if ($bTreatAsAtom) 2355 { 2356 $sAtomBuilder .= \strtr( 2357 \substr($this->sResponseBuffer, $iPos, $iClosingPos - $iPos + 1), 2358 array('\\\\' => '\\', '\\"' => '"') 2359 ); 2360 } 2361 else 2362 { 2363 $aList[] = \strtr( 2364 \substr($this->sResponseBuffer, $iPos + 1, $iClosingPos - $iPos - 1), 2365 array('\\\\' => '\\', '\\"' => '"') 2366 ); 2367 } 2368 2369 $iPos = $iClosingPos + 1; 2370 break; 2371 } 2372 } 2373 2374 if (!$bIsQuotedParsed) 2375 { 2376 $iPos = $iBufferEndIndex; 2377 } 2378 2379 $sPreviousAtomUpperCase = null; 2380 break; 2381 2382 case 'GOTO_DEFAULT' === $sChar: 2383 default: 2384 $iCharBlockStartPos = $iPos; 2385 2386 if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) 2387 { 2388 $iPos = $iBufferEndIndex; 2389 2390 while ($iPos > $iCharBlockStartPos && $this->sResponseBuffer[$iCharBlockStartPos] === ' ') 2391 { 2392 $iCharBlockStartPos++; 2393 } 2394 } 2395 2396 $bIsAtomDone = false; 2397 while (!$bIsAtomDone && ($iPos <= $iBufferEndIndex)) 2398 { 2399 $sCharDef = $this->sResponseBuffer[$iPos]; 2400 switch (true) 2401 { 2402 case ('[' === $sCharDef || ']' === $sCharDef || '(' === $sCharDef || ')' === $sCharDef) && 2403 $this->skipBracketParse($oImapResponse): 2404 $iPos++; 2405 break; 2406 case '[' === $sCharDef: 2407 if (null === $sAtomBuilder) 2408 { 2409 $sAtomBuilder = ''; 2410 } 2411 2412 $sAtomBuilder .= \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos + 1); 2413 2414 $iPos++; 2415 $this->iResponseBufParsedPos = $iPos; 2416 2417 $sListBlock = $this->partialParseResponseBranch($mNull, true, 2418 null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), '['); 2419 2420 if (null !== $sListBlock) 2421 { 2422 $sAtomBuilder .= $sListBlock.']'; 2423 } 2424 2425 $this->Logger()->Write('$sAtomBuilder='.$sAtomBuilder); 2426 2427 $iPos = $this->iResponseBufParsedPos; 2428 $iCharBlockStartPos = $iPos; 2429 break; 2430 case ' ' === $sCharDef: 2431 case ')' === $sCharDef && '(' === $sOpenBracket: 2432 case ']' === $sCharDef && '[' === $sOpenBracket: 2433 $bIsAtomDone = true; 2434 break; 2435 default: 2436 $iPos++; 2437 break; 2438 } 2439 } 2440 2441 if ($iPos > $iCharBlockStartPos || null !== $sAtomBuilder) 2442 { 2443 $sLastCharBlock = \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos); 2444 if (null === $sAtomBuilder) 2445 { 2446 $aList[] = $sLastCharBlock; 2447 $sPreviousAtomUpperCase = $sLastCharBlock; 2448 } 2449 else 2450 { 2451 $sAtomBuilder .= $sLastCharBlock; 2452 2453 if (!$bTreatAsAtom) 2454 { 2455 $aList[] = $sAtomBuilder; 2456 $sPreviousAtomUpperCase = $sAtomBuilder; 2457 $sAtomBuilder = null; 2458 } 2459 } 2460 2461 if (null !== $oImapResponse) 2462 { 2463// if (1 === \count($aList)) 2464 if (!$bCountOneInited && 1 === \count($aList)) 2465// if (isset($aList[0]) && !isset($aList[1])) // fast 1 === \count($aList) 2466 { 2467 $bCountOneInited = true; 2468 2469 $oImapResponse->Tag = $aList[0]; 2470 if ('+' === $oImapResponse->Tag) 2471 { 2472 $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::CONTINUATION; 2473 } 2474 else if ('*' === $oImapResponse->Tag) 2475 { 2476 $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNTAGGED; 2477 } 2478 else if ($this->getCurrentTag() === $oImapResponse->Tag) 2479 { 2480 $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::TAGGED; 2481 } 2482 else 2483 { 2484 $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNKNOWN; 2485 } 2486 } 2487// else if (2 === \count($aList)) 2488 else if (!$bCountTwoInited && 2 === \count($aList)) 2489// else if (isset($aList[1]) && !isset($aList[2])) // fast 2 === \count($aList) 2490 { 2491 $bCountTwoInited = true; 2492 2493 $oImapResponse->StatusOrIndex = strtoupper($aList[1]); 2494 2495 if ($oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::OK || 2496 $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::NO || 2497 $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::BAD || 2498 $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::BYE || 2499 $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::PREAUTH) 2500 { 2501 $oImapResponse->IsStatusResponse = true; 2502 } 2503 } 2504 else if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oImapResponse->ResponseType) 2505 { 2506 $oImapResponse->HumanReadable = $sLastCharBlock; 2507 } 2508 else if ($oImapResponse->IsStatusResponse) 2509 { 2510 $oImapResponse->HumanReadable = $sLastCharBlock; 2511 } 2512 } 2513 } 2514 } 2515 } 2516 2517 $this->iResponseBufParsedPos = $iPos; 2518 if (null !== $oImapResponse) 2519 { 2520 $this->bNeedNext = true; 2521 $this->iResponseBufParsedPos = 0; 2522 } 2523 2524 if (100000 < $iDebugCount) 2525 { 2526 $this->Logger()->Write('PartialParseOverResult: '.$iDebugCount, \MailSo\Log\Enumerations\Type::ERROR); 2527 } 2528 2529 return $bTreatAsAtom ? $sAtomBuilder : $aList; 2530 } 2531 2532 /** 2533 * @param string $sParent 2534 * @param string $sLiteralAtomUpperCase 2535 * @param resource $rImapStream 2536 * @param int $iLiteralLen 2537 * 2538 * @return bool 2539 */ 2540 private function partialResponseLiteralCallbackCallable($sParent, $sLiteralAtomUpperCase, $rImapStream, $iLiteralLen) 2541 { 2542 $sLiteralAtomUpperCasePeek = ''; 2543 if (0 === \strpos($sLiteralAtomUpperCase, 'BODY')) 2544 { 2545 $sLiteralAtomUpperCasePeek = \str_replace('BODY', 'BODY.PEEK', $sLiteralAtomUpperCase); 2546 } 2547 2548 $sFetchKey = ''; 2549 if (\is_array($this->aFetchCallbacks)) 2550 { 2551 if (0 < \strlen($sLiteralAtomUpperCasePeek) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCasePeek])) 2552 { 2553 $sFetchKey = $sLiteralAtomUpperCasePeek; 2554 } 2555 else if (0 < \strlen($sLiteralAtomUpperCase) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCase])) 2556 { 2557 $sFetchKey = $sLiteralAtomUpperCase; 2558 } 2559 } 2560 2561 $bResult = false; 2562 if (0 < \strlen($sFetchKey) && '' !== $this->aFetchCallbacks[$sFetchKey] && 2563 \is_callable($this->aFetchCallbacks[$sFetchKey])) 2564 { 2565 $rImapLiteralStream = 2566 \MailSo\Base\StreamWrappers\Literal::CreateStream($rImapStream, $iLiteralLen); 2567 2568 $bResult = true; 2569 $this->writeLog('Start Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. 2570 ' - try to read '.$iLiteralLen.' bytes.', \MailSo\Log\Enumerations\Type::NOTE); 2571 2572 $this->bRunningCallback = true; 2573 2574 try 2575 { 2576 \call_user_func($this->aFetchCallbacks[$sFetchKey], 2577 $sParent, $sLiteralAtomUpperCase, $rImapLiteralStream); 2578 } 2579 catch (\Exception $oException) 2580 { 2581 $this->writeLog('Callback Exception', \MailSo\Log\Enumerations\Type::NOTICE); 2582 $this->writeLogException($oException); 2583 } 2584 2585 if (\is_resource($rImapLiteralStream)) 2586 { 2587 $iNotReadLiteralLen = 0; 2588 2589 $bFeof = \feof($rImapLiteralStream); 2590 $this->writeLog('End Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. 2591 ' - feof = '.($bFeof ? 'good' : 'BAD'), $bFeof ? 2592 \MailSo\Log\Enumerations\Type::NOTE : \MailSo\Log\Enumerations\Type::WARNING); 2593 2594 if (!$bFeof) 2595 { 2596 while (!@\feof($rImapLiteralStream)) 2597 { 2598 $sBuf = @\fread($rImapLiteralStream, 1024 * 1024); 2599 if (false === $sBuf || 0 === \strlen($sBuf) || null === $sBuf) 2600 { 2601 break; 2602 } 2603 2604 \MailSo\Base\Utils::ResetTimeLimit(); 2605 $iNotReadLiteralLen += \strlen($sBuf); 2606 } 2607 2608 if (\is_resource($rImapLiteralStream) && !@\feof($rImapLiteralStream)) 2609 { 2610 @\stream_get_contents($rImapLiteralStream); 2611 } 2612 } 2613 2614 if (\is_resource($rImapLiteralStream)) 2615 { 2616 @\fclose($rImapLiteralStream); 2617 } 2618 2619 if ($iNotReadLiteralLen > 0) 2620 { 2621 $this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.', 2622 \MailSo\Log\Enumerations\Type::WARNING); 2623 } 2624 } 2625 else 2626 { 2627 $this->writeLog('Literal stream is not resource after callback.', 2628 \MailSo\Log\Enumerations\Type::WARNING); 2629 } 2630 2631 \MailSo\Base\Loader::IncStatistic('NetRead', $iLiteralLen); 2632 2633 $this->bRunningCallback = false; 2634 } 2635 2636 return $bResult; 2637 } 2638 2639 /** 2640 * @param array $aParams = null 2641 * 2642 * @return string 2643 */ 2644 private function prepearParamLine($aParams = array()) 2645 { 2646 $sReturn = ''; 2647 if (\is_array($aParams) && 0 < \count($aParams)) 2648 { 2649 foreach ($aParams as $mParamItem) 2650 { 2651 if (\is_array($mParamItem) && 0 < \count($mParamItem)) 2652 { 2653 $sReturn .= ' ('.\trim($this->prepearParamLine($mParamItem)).')'; 2654 } 2655 else if (\is_string($mParamItem)) 2656 { 2657 $sReturn .= ' '.$mParamItem; 2658 } 2659 } 2660 } 2661 return $sReturn; 2662 } 2663 2664 /** 2665 * @return string 2666 */ 2667 private function getNewTag() 2668 { 2669 $this->iTagCount++; 2670 return $this->getCurrentTag(); 2671 } 2672 2673 /** 2674 * @return string 2675 */ 2676 private function getCurrentTag() 2677 { 2678 return self::TAG_PREFIX.$this->iTagCount; 2679 } 2680 2681 /** 2682 * @param string $sStringForEscape 2683 * 2684 * @return string 2685 */ 2686 public function EscapeString($sStringForEscape) 2687 { 2688 return '"'.\str_replace(array('\\', '"'), array('\\\\', '\\"'), $sStringForEscape).'"'; 2689 } 2690 2691 /** 2692 * @return string 2693 */ 2694 protected function getLogName() 2695 { 2696 return 'IMAP'; 2697 } 2698 2699 /** 2700 * @param \MailSo\Log\Logger $oLogger 2701 * 2702 * @return \MailSo\Imap\ImapClient 2703 * 2704 * @throws \MailSo\Base\Exceptions\InvalidArgumentException 2705 */ 2706 public function SetLogger($oLogger) 2707 { 2708 parent::SetLogger($oLogger); 2709 2710 return $this; 2711 } 2712 2713 /** 2714 * @param resource $rConnect 2715 * @param array $aCapabilityItems = array() 2716 * 2717 * @return \MailSo\Imap\ImapClient 2718 */ 2719 public function TestSetValues($rConnect, $aCapabilityItems = array()) 2720 { 2721 $this->rConnect = $rConnect; 2722 $this->aCapabilityItems = $aCapabilityItems; 2723 2724 return $this; 2725 } 2726 2727 /** 2728 * @param string $sEndTag = null 2729 * @param string $bFindCapa = false 2730 * 2731 * @return array 2732 */ 2733 public function TestParseResponseWithValidationProxy($sEndTag = null, $bFindCapa = false) 2734 { 2735 return $this->parseResponseWithValidation($sEndTag, $bFindCapa); 2736 } 2737} 2738