See Release Notes
Long Term Support Release
1 <?php 2 /** 3 * Copyright 2009-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file LICENSE for license information (LGPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 7 * 8 * --------------------------------------------------------------------------- 9 * 10 * Based on the PEAR Net_POP3 package (version 1.3.6) by: 11 * Richard Heyes <richard@phpguru.org> 12 * Damian Fernandez Sosa <damlists@cnba.uba.ar> 13 * 14 * Copyright (c) 2002, Richard Heyes 15 * All rights reserved. 16 * 17 * Redistribution and use in source and binary forms, with or without 18 * modification, are permitted provided that the following conditions 19 * are met: 20 * 21 * o Redistributions of source code must retain the above copyright 22 * notice, this list of conditions and the following disclaimer. 23 * o Redistributions in binary form must reproduce the above copyright 24 * notice, this list of conditions and the following disclaimer in the 25 * documentation and/or other materials provided with the distribution. 26 * o The names of the authors may not be used to endorse or promote 27 * products derived from this software without specific prior written 28 * permission. 29 * 30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 31 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 32 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 33 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 34 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 36 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 37 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 38 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 39 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 40 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 41 * 42 * --------------------------------------------------------------------------- 43 * 44 * @category Horde 45 * @copyright 2002 Richard Heyes 46 * @copyright 2009-2017 Horde LLC 47 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 48 * @package Imap_Client 49 */ 50 51 /** 52 * An interface to a POP3 server using PHP functions. 53 * 54 * It is an abstraction layer allowing POP3 commands to be used based on 55 * IMAP equivalents. 56 * 57 * This driver implements the following POP3-related RFCs: 58 * <pre> 59 * - STD 53/RFC 1939: POP3 specification 60 * - RFC 2195: CRAM-MD5 authentication 61 * - RFC 2449: POP3 extension mechanism 62 * - RFC 2595/4616: PLAIN authentication 63 * - RFC 2831: DIGEST-MD5 SASL Authentication (obsoleted by RFC 6331) 64 * - RFC 3206: AUTH/SYS response codes 65 * - RFC 4616: AUTH=PLAIN 66 * - RFC 5034: POP3 SASL 67 * - RFC 5802: AUTH=SCRAM-SHA-1 68 * - RFC 6856: UTF8, LANG 69 * </pre> 70 * 71 * @author Richard Heyes <richard@phpguru.org> 72 * @author Michael Slusarz <slusarz@horde.org> 73 * @category Horde 74 * @copyright 2002 Richard Heyes 75 * @copyright 2009-2017 Horde LLC 76 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 77 * @package Imap_Client 78 */ 79 class Horde_Imap_Client_Socket_Pop3 extends Horde_Imap_Client_Base 80 { 81 /* Internal key used to store mailbox level cache data. \1 is not a valid 82 * ID in POP3, so it should be safe to use. */ 83 const MBOX_CACHE = "\1mbox"; 84 85 /** 86 * The default ports to use for a connection. 87 * 88 * @var array 89 */ 90 protected $_defaultPorts = array(110, 995); 91 92 /** 93 * The list of deleted messages. 94 * 95 * @var array 96 */ 97 protected $_deleted = array(); 98 99 /** 100 * This object returns POP3 Fetch data objects. 101 * 102 * @var string 103 */ 104 protected $_fetchDataClass = 'Horde_Imap_Client_Data_Fetch_Pop3'; 105 106 /** 107 */ 108 public function __get($name) 109 { 110 $out = parent::__get($name); 111 112 switch ($name) { 113 case 'url': 114 $out->protocol = 'pop3'; 115 break; 116 } 117 118 return $out; 119 } 120 121 /** 122 */ 123 protected function _initCache($current = false) 124 { 125 return parent::_initCache($current) && 126 $this->_capability('UIDL'); 127 } 128 129 /** 130 */ 131 public function getIdsOb($ids = null, $sequence = false) 132 { 133 return new Horde_Imap_Client_Ids_Pop3($ids, $sequence); 134 } 135 136 /** 137 */ 138 protected function _initCapability() 139 { 140 $this->_connect(); 141 142 $c = new Horde_Imap_Client_Data_Capability(); 143 144 try { 145 $res = $this->_sendLine('CAPA', array( 146 'multiline' => 'array' 147 )); 148 149 foreach ($res['data'] as $val) { 150 $prefix = explode(' ', $val); 151 $c->add($prefix[0], array_slice($prefix, 1)); 152 } 153 } catch (Horde_Imap_Client_Exception $e) { 154 $this->_temp['no_capa'] = true; 155 156 /* Need to probe for capabilities if CAPA command is not 157 * available. */ 158 $c->add('USER'); 159 160 /* Capability sniffing only guaranteed after authentication is 161 * completed (if any). */ 162 if (!empty($this->_init['authmethod'])) { 163 $this->_pop3Cache('uidl'); 164 if (empty($this->_temp['no_uidl'])) { 165 $c->add('UIDL'); 166 } 167 168 $this->_pop3Cache('top', 1); 169 if (empty($this->_temp['no_top'])) { 170 $c->add('TOP'); 171 } 172 } 173 } 174 175 $this->_setInit('capability', $c); 176 } 177 178 /** 179 */ 180 protected function _noop() 181 { 182 $this->_sendLine('NOOP'); 183 } 184 185 /** 186 * @throws Horde_Imap_Client_Exception_NoSupportPop3 187 */ 188 protected function _getNamespaces() 189 { 190 throw new Horde_Imap_Client_Exception_NoSupportPop3('Namespaces'); 191 } 192 193 /** 194 */ 195 protected function _login() 196 { 197 /* Blank passwords are not allowed, so no need to even try 198 * authentication to determine this. */ 199 if (!strlen($this->getParam('password'))) { 200 throw new Horde_Imap_Client_Exception( 201 Horde_Imap_Client_Translation::r("No password provided."), 202 Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED 203 ); 204 } 205 206 $this->_connect(); 207 208 $first_login = empty($this->_init['authmethod']); 209 210 // Switch to secure channel if using TLS. 211 if (!$this->isSecureConnection()) { 212 $secure = $this->getParam('secure'); 213 214 if (($secure === 'tls') || $secure === true) { 215 // Switch over to a TLS connection. 216 if ($first_login && !$this->_capability('STLS')) { 217 if ($secure === 'tls') { 218 throw new Horde_Imap_Client_Exception( 219 Horde_Imap_Client_Translation::r("Could not open secure connection to the POP3 server.") . ' ' . Horde_Imap_Client_Translation::r("Server does not support secure connections."), 220 Horde_Imap_Client_Exception::LOGIN_TLSFAILURE 221 ); 222 } else { 223 $this->setParam('secure', false); 224 } 225 } else { 226 $this->_sendLine('STLS'); 227 228 $this->setParam('secure', 'tls'); 229 230 if (!$this->_connection->startTls()) { 231 $this->logout(); 232 throw new Horde_Imap_Client_Exception( 233 Horde_Imap_Client_Translation::r("Could not open secure connection to the POP3 server."), 234 Horde_Imap_Client_Exception::LOGIN_TLSFAILURE 235 ); 236 } 237 $this->_debug->info('Successfully completed TLS negotiation.'); 238 } 239 240 // Expire cached CAPABILITY information 241 $this->_setInit('capability'); 242 } else { 243 $this->setParam('secure', false); 244 } 245 } 246 247 if ($first_login) { 248 /* At least one server (Dovecot 1.x) may return SASL capability 249 * with no arguments. */ 250 $auth_mech = $this->_capability()->getParams('SASL'); 251 252 if (isset($this->_temp['pop3timestamp'])) { 253 $auth_mech[] = 'APOP'; 254 } 255 256 $auth_mech[] = 'USER'; 257 258 /* Enable UTF-8 mode (RFC 6856). MUST occur after STLS is 259 * issued. */ 260 if ($this->_capability('UTF8')) { 261 try { 262 $this->_sendLine('UTF8'); 263 $this->_temp['utf8'] = true; 264 } catch (Horde_Imap_Client_Exception $e) { 265 /* If server responds to UTF8 command with error, 266 * fallback to legacy non-UTF8 behavior. */ 267 } 268 } 269 } else { 270 $auth_mech = array($this->_init['authmethod']); 271 } 272 273 foreach ($auth_mech as $method) { 274 try { 275 $this->_tryLogin($method); 276 $this->_setInit('authmethod', $method); 277 278 if (!empty($this->_temp['no_capa']) || 279 !$this->_capability('UIDL')) { 280 $this->_setInit('capability'); 281 } 282 283 return true; 284 } catch (Horde_Imap_Client_Exception $e) { 285 if (!empty($this->_init['authmethod']) && 286 ($e->getCode() != $e::LOGIN_UNAVAILABLE) && 287 ($e->getCode() != $e::POP3_TEMP_ERROR)) { 288 $this->_setInit(); 289 return $this->login(); 290 } 291 } 292 } 293 294 throw new Horde_Imap_Client_Exception( 295 Horde_Imap_Client_Translation::r("POP3 server denied authentication."), 296 $e->getCode() ?: $e::LOGIN_AUTHENTICATIONFAILED 297 ); 298 } 299 300 /** 301 * Connects to the server. 302 * 303 * @throws Horde_Imap_Client_Exception 304 */ 305 protected function _connect() 306 { 307 if (!is_null($this->_connection)) { 308 return; 309 } 310 311 try { 312 $this->_connection = new Horde_Imap_Client_Socket_Connection_Pop3( 313 $this->getParam('hostspec'), 314 $this->getParam('port'), 315 $this->getParam('timeout'), 316 $this->getParam('secure'), 317 $this->getParam('context'), 318 array( 319 'debug' => $this->_debug 320 ) 321 ); 322 } catch (Horde\Socket\Client\Exception $e) { 323 $e2 = new Horde_Imap_Client_Exception( 324 Horde_Imap_Client_Translation::r("Error connecting to mail server."), 325 Horde_Imap_Client_Exception::SERVER_CONNECT 326 ); 327 $e2->details = $e->details; 328 throw $e2; 329 } 330 331 $line = $this->_getResponse(); 332 333 // Check for string matching APOP timestamp 334 if (preg_match('/<.+@.+>/U', $line['resp'], $matches)) { 335 $this->_temp['pop3timestamp'] = $matches[0]; 336 } 337 } 338 339 /** 340 * Authenticate to the POP3 server. 341 * 342 * @param string $method POP3 login method. 343 * 344 * @throws Horde_Imap_Client_Exception 345 */ 346 protected function _tryLogin($method) 347 { 348 $username = $this->getParam('username'); 349 $password = $this->getParam('password'); 350 351 switch ($method) { 352 case 'CRAM-MD5': 353 case 'CRAM-SHA1': 354 case 'CRAM-SHA256': 355 // RFC 5034: CRAM-MD5 356 // CRAM-SHA1 & CRAM-SHA256 supported by Courier SASL library 357 $challenge = $this->_sendLine('AUTH ' . $method); 358 $response = base64_encode($username . ' ' . hash_hmac(Horde_String::lower(substr($method, 5)), base64_decode(substr($challenge['resp'], 2)), $password, true)); 359 $this->_sendLine($response, array( 360 'debug' => sprintf('[AUTH Response (username: %s)]', $username) 361 )); 362 break; 363 364 case 'DIGEST-MD5': 365 // RFC 2831; Obsoleted by RFC 6331 366 $challenge = $this->_sendLine('AUTH DIGEST-MD5'); 367 $response = base64_encode(new Horde_Imap_Client_Auth_DigestMD5( 368 $username, 369 $password, 370 base64_decode(substr($challenge['resp'], 2)), 371 $this->getParam('hostspec'), 372 'pop3' 373 )); 374 $sresponse = $this->_sendLine($response, array( 375 'debug' => sprintf('[AUTH Response (username: %s)]', $username) 376 )); 377 if (stripos(base64_decode(substr($sresponse['resp'], 2)), 'rspauth=') === false) { 378 throw new Horde_Imap_Client_Exception( 379 Horde_Imap_Client_Translation::r("Unexpected response from server when authenticating."), 380 Horde_Imap_Client_Exception::SERVER_CONNECT 381 ); 382 } 383 384 /* POP3 doesn't use protocol's third step. */ 385 $this->_sendLine(''); 386 break; 387 388 case 'LOGIN': 389 // RFC 4616 (AUTH=PLAIN) & 5034 (POP3 SASL) 390 $this->_sendLine('AUTH LOGIN'); 391 $this->_sendLine(base64_encode($username)); 392 $this->_sendLine(base64_encode($password), array( 393 'debug' => sprintf('[AUTH Password (username: %s)]', $username) 394 )); 395 break; 396 397 case 'PLAIN': 398 // RFC 5034 399 $this->_sendLine('AUTH PLAIN ' . base64_encode(implode("\0", array( 400 $username, 401 $username, 402 $password 403 ))), array( 404 'debug' => sprintf('AUTH PLAIN [Auth Response (username: %s)]', $username) 405 )); 406 break; 407 408 case 'APOP': 409 /* If UTF8 (+ USER) is active, and non-ASCII exists, need to apply 410 * SASLprep to username/password. RFC 6856[2.2]. Reject if 411 * UTF8 (+ USER) is not supported and 8-bit characters exist. */ 412 if (Horde_Mime::is8bit($username) || 413 Horde_Mime::is8bit($password)) { 414 if (empty($this->_temp['utf8']) || 415 !$this->_capability('UTF8', 'USER') || 416 !class_exists('Horde_Stringprep')) { 417 $error = true; 418 } else { 419 Horde_Stringprep::autoload(); 420 $saslprep = new Znerol\Component\Stringprep\Profile\SASLprep(); 421 422 try { 423 $username = $saslprep->apply( 424 $username, 425 'UTF-8', 426 Znerol\Compnonent\Stringprep\Profile::MODE_QUERY 427 ); 428 $password = $saslprep->apply( 429 $password, 430 'UTF-8', 431 Znerol\Compnonent\Stringprep\Profile::MODE_STORE 432 ); 433 $error = false; 434 } catch (Znerol\Component\Stringprep\ProfileException $e) { 435 $error = true; 436 } 437 } 438 439 if ($error) { 440 throw new Horde_Imap_Client_Exception( 441 Horde_Imap_Client_Translation::r("Authentication failed."), 442 Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED 443 ); 444 } 445 } 446 447 // RFC 1939 [7] 448 $this->_sendLine('APOP ' . $username . ' ' . 449 hash('md5', $this->_temp['pop3timestamp'] . $password)); 450 break; 451 452 case 'USER': 453 /* POP3 servers without UTF8 (+ USER) does not accept non-ASCII 454 * in USER/PASS. RFC 6856[2.2] */ 455 if ((empty($this->_temp['utf8']) || 456 !$this->_capability('UTF8', 'USER')) && 457 (Horde_Mime::is8bit($username) || 458 Horde_Mime::is8bit($password))) { 459 throw new Horde_Imap_Client_Exception( 460 Horde_Imap_Client_Translation::r("Authentication failed."), 461 Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED 462 ); 463 } 464 465 // RFC 1939 [7] 466 $this->_sendLine('USER ' . $username); 467 $this->_sendLine('PASS ' . $password, array( 468 'debug' => 'PASS [Password]' 469 )); 470 break; 471 472 case 'SCRAM-SHA-1': 473 $scram = new Horde_Imap_Client_Auth_Scram( 474 $username, 475 $password, 476 'SHA1' 477 ); 478 479 $c1 = $this->_sendLine( 480 'AUTH ' . $method . ' ' . base64_encode($scram->getClientFirstMessage()) 481 ); 482 483 $sr1 = base64_decode(substr($c1['resp'], 2)); 484 if (!$scram->parseServerFirstMessage($sr1)) { 485 throw new Horde_Imap_Client_Exception( 486 Horde_Imap_Client_Translation::r("Authentication failed."), 487 Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED 488 ); 489 } 490 491 $c2 = $this->_sendLine( 492 base64_encode($scram->getClientFinalMessage()) 493 ); 494 495 $sr2 = base64_decode(substr($c2['resp'], 2)); 496 if (!$scram->parseServerFirstMessage($sr)) { 497 throw new Horde_Imap_Client_Exception( 498 Horde_Imap_Client_Translation::r("Authentication failed."), 499 Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED 500 ); 501 502 /* This means authentication passed, according to the server, 503 * but the server signature is incorrect. This indicates that 504 * server verification has failed. Immediately disconnect from 505 * the server, since this is a possible security issue. */ 506 $this->logout(); 507 throw new Horde_Imap_Client_Exception( 508 Horde_Imap_Client_Translation::r("Server failed verification check."), 509 Horde_Imap_Client_Exception::LOGIN_SERVER_VERIFICATION_FAILED 510 ); 511 } 512 513 $this->_sendLine(''); 514 break; 515 516 default: 517 $e = new Horde_Imap_Client_Exception( 518 Horde_Imap_Client_Translation::r("Unknown authentication method: %s"), 519 Horde_Imap_Client_Exception::SERVER_CONNECT 520 ); 521 $e->messagePrintf(array($method)); 522 throw $e; 523 } 524 } 525 526 /** 527 */ 528 protected function _logout() 529 { 530 try { 531 $this->_sendLine('QUIT'); 532 } catch (Horde_Imap_Client_Exception $e) {} 533 $this->_deleted = array(); 534 } 535 536 /** 537 * @throws Horde_Imap_Client_Exception_NoSupportPop3 538 */ 539 protected function _sendID($info) 540 { 541 throw new Horde_Imap_Client_Exception_NoSupportPop3('ID command'); 542 } 543 544 /** 545 * Return implementation information from the POP3 server (RFC 2449 [6.9]). 546 */ 547 protected function _getID() 548 { 549 return ($id = $this->_capability()->getParams('IMPLEMENTATION')) 550 ? array('implementation' => reset($id)) 551 : array(); 552 } 553 554 /** 555 * @throws Horde_Imap_Client_Exception_NoSupportPop3 556 */ 557 protected function _setLanguage($langs) 558 { 559 // RFC 6856 [3] 560 if (!$this->_capability('LANG')) { 561 throw new Horde_Imap_Client_Exception_NoSupportPop3('LANGUAGE extension'); 562 } 563 564 foreach ($langs as $val) { 565 try { 566 $this->_sendLine('LANG ' . $val); 567 $this->_temp['lang'] = $val; 568 } catch (Horde_Imap_Client_Exception $e) { 569 // Setting language failed - move on to next one. 570 } 571 } 572 573 return $this->_getLanguage(false); 574 } 575 576 /** 577 * @throws Horde_Imap_Client_Exception_NoSupportPop3 578 */ 579 protected function _getLanguage($list) 580 { 581 // RFC 6856 [3] 582 if (!$this->_capability('LANG')) { 583 throw new Horde_Imap_Client_Exception_NoSupportPop3('LANGUAGE extension'); 584 } 585 586 if (!$list) { 587 return isset($this->_temp['lang']) 588 ? $this->_temp['lang'] 589 : null; 590 } 591 592 $langs = array(); 593 594 try { 595 $res = $this->_sendLine('LANG', array( 596 'multiline' => 'array' 597 )); 598 599 foreach ($res['data'] as $val) { 600 $parts = explode(' ', $val); 601 $langs[] = $parts[0]; 602 // $parts[1] - lanuage description (not used) 603 } 604 } catch (Horde_Imap_Client_Exception $e) { 605 // Ignore: language listing might fail. RFC 6856 [3.3] 606 } 607 608 return $langs; 609 } 610 611 /** 612 * @throws Horde_Imap_Client_Exception_NoSupportPop3 613 */ 614 protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox, $mode) 615 { 616 if ($mailbox != 'INBOX') { 617 throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX'); 618 } 619 $this->_changeSelected($mailbox, $mode); 620 } 621 622 /** 623 * @throws Horde_Imap_Client_Exception_NoSupportPop3 624 */ 625 protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox, $opts) 626 { 627 throw new Horde_Imap_Client_Exception_NoSupportPop3('Creating mailboxes'); 628 } 629 630 /** 631 * @throws Horde_Imap_Client_Exception_NoSupportPop3 632 */ 633 protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox) 634 { 635 throw new Horde_Imap_Client_Exception_NoSupportPop3('Deleting mailboxes'); 636 } 637 638 /** 639 * @throws Horde_Imap_Client_Exception_NoSupportPop3 640 */ 641 protected function _renameMailbox(Horde_Imap_Client_Mailbox $old, 642 Horde_Imap_Client_Mailbox $new) 643 { 644 throw new Horde_Imap_Client_Exception_NoSupportPop3('Renaming mailboxes'); 645 } 646 647 /** 648 * @throws Horde_Imap_Client_Exception_NoSupportPop3 649 */ 650 protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox, 651 $subscribe) 652 { 653 throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX'); 654 } 655 656 /** 657 */ 658 protected function _listMailboxes($pattern, $mode, $options) 659 { 660 if (empty($options['flat'])) { 661 return array( 662 'INBOX' => array( 663 'attributes' => array(), 664 'delimiter' => '', 665 'mailbox' => Horde_Imap_Client_Mailbox::get('INBOX') 666 ) 667 ); 668 } 669 670 return array('INBOX' => Horde_Imap_Client_Mailbox::get('INBOX')); 671 } 672 673 /** 674 * @param integer $flags This driver only supports the options listed 675 * under Horde_Imap_Client::STATUS_ALL. 676 * @throws Horde_Imap_Client_Exception_NoSupportPop3 677 */ 678 protected function _status($mboxes, $flags) 679 { 680 if ((count($mboxes) > 1) || (reset($mboxes) != 'INBOX')) { 681 throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX'); 682 } 683 684 $this->openMailbox('INBOX'); 685 686 $ret = array(); 687 688 if ($flags & Horde_Imap_Client::STATUS_MESSAGES) { 689 $res = $this->_pop3Cache('stat'); 690 $ret['messages'] = $res['msgs']; 691 } 692 693 if ($flags & Horde_Imap_Client::STATUS_RECENT) { 694 $res = $this->_pop3Cache('stat'); 695 $ret['recent'] = $res['msgs']; 696 } 697 698 // No need for STATUS_UIDNEXT_FORCE handling since STATUS_UIDNEXT will 699 // always return a value. 700 $uidl = $this->_capability('UIDL'); 701 if ($flags & Horde_Imap_Client::STATUS_UIDNEXT) { 702 if ($uidl) { 703 $ctx = hash_init('md5'); 704 foreach ($this->_pop3Cache('uidl') as $key => $val) { 705 hash_update($ctx, '|' . $key . '|' . $val); 706 } 707 $ret['uidnext'] = hash_final($ctx); 708 } else { 709 $res = $this->_pop3Cache('stat'); 710 $ret['uidnext'] = $res['msgs'] + 1; 711 } 712 } 713 714 if ($flags & Horde_Imap_Client::STATUS_UIDVALIDITY) { 715 $ret['uidvalidity'] = $uidl 716 ? 1 717 : microtime(true); 718 } 719 720 if ($flags & Horde_Imap_Client::STATUS_UNSEEN) { 721 $ret['unseen'] = 0; 722 } 723 724 return array('INBOX' => $ret); 725 } 726 727 /** 728 * @throws Horde_Imap_Client_Exception_NoSupportPop3 729 */ 730 protected function _append(Horde_Imap_Client_Mailbox $mailbox, $data, 731 $options) 732 { 733 throw new Horde_Imap_Client_Exception_NoSupportPop3('Appending messages'); 734 } 735 736 /** 737 */ 738 protected function _check() 739 { 740 $this->noop(); 741 } 742 743 /** 744 */ 745 protected function _close($options) 746 { 747 if (!empty($options['expunge'])) { 748 $this->logout(); 749 } 750 } 751 752 /** 753 * @param array $options Additional options. 'ids' has no effect in this 754 * driver. 755 */ 756 protected function _expunge($options) 757 { 758 $msg_list = $this->_deleted; 759 $this->logout(); 760 return empty($options['list']) 761 ? null 762 : $msg_list; 763 } 764 765 /** 766 * @throws Horde_Imap_Client_Exception_NoSupportPop3 767 */ 768 protected function _search($query, $options) 769 { 770 $sort = empty($options['sort']) 771 ? null 772 : reset($options['sort']); 773 774 // Only support a single query: an ALL search sorted by sequence. 775 if ((strval($options['_query']['query']) != 'ALL') || 776 ($sort && 777 ((count($options['sort']) > 1) || 778 ($sort != Horde_Imap_Client::SORT_SEQUENCE)))) { 779 throw new Horde_Imap_Client_Exception_NoSupportPop3('Server search'); 780 } 781 782 $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES); 783 $res = range(1, $status['messages']); 784 785 if (empty($options['sequence'])) { 786 $tmp = array(); 787 $uidllist = $this->_pop3Cache('uidl'); 788 foreach ($res as $val) { 789 $tmp[] = $uidllist[$val]; 790 } 791 $res = $tmp; 792 } 793 794 if (!empty($options['partial'])) { 795 $partial = $this->getIdsOb($options['partial'], true); 796 $min = $partial->min - 1; 797 $res = array_slice($res, $min, $partial->max - $min); 798 } 799 800 $ret = array(); 801 foreach ($options['results'] as $val) { 802 switch ($val) { 803 case Horde_Imap_Client::SEARCH_RESULTS_COUNT: 804 $ret['count'] = count($res); 805 break; 806 807 case Horde_Imap_Client::SEARCH_RESULTS_MATCH: 808 $ret['match'] = $this->getIdsOb($res); 809 break; 810 811 case Horde_Imap_Client::SEARCH_RESULTS_MAX: 812 $ret['max'] = empty($res) ? null : max($res); 813 break; 814 815 case Horde_Imap_Client::SEARCH_RESULTS_MIN: 816 $ret['min'] = empty($res) ? null : min($res); 817 break; 818 } 819 } 820 821 return $ret; 822 } 823 824 /** 825 * @throws Horde_Imap_Client_Exception_NoSupportPop3 826 */ 827 protected function _setComparator($comparator) 828 { 829 throw new Horde_Imap_Client_Exception_NoSupportPop3('Search comparators'); 830 } 831 832 /** 833 * @throws Horde_Imap_Client_Exception_NoSupportPop3 834 */ 835 protected function _getComparator() 836 { 837 throw new Horde_Imap_Client_Exception_NoSupportPop3('Search comparators'); 838 } 839 840 /** 841 * @throws Horde_Imap_Client_Exception_NoSupportPop3 842 */ 843 protected function _thread($options) 844 { 845 throw new Horde_Imap_Client_Exception_NoSupportPop3('Server threading'); 846 } 847 848 /** 849 */ 850 protected function _fetch(Horde_Imap_Client_Fetch_Results $results, 851 $queries) 852 { 853 foreach ($queries as $options) { 854 $this->_fetchCmd($results, $options); 855 } 856 857 $this->_updateCache($results); 858 } 859 860 /** 861 * Fetch data for a given fetch query. 862 * 863 * @param Horde_Imap_Client_Fetch_Results $results Fetch results. 864 * @param array $options Fetch query options. 865 */ 866 protected function _fetchCmd(Horde_Imap_Client_Fetch_Results $results, 867 $options) 868 { 869 // Grab sequence IDs - IDs will always be the message number for 870 // POP3 fetch commands. 871 $seq_ids = $this->_getSeqIds($options['ids']); 872 if (empty($seq_ids)) { 873 return; 874 } 875 876 $lookup = $options['ids']->sequence 877 ? array_combine($seq_ids, $seq_ids) 878 : $this->_pop3Cache('uidl'); 879 880 foreach ($options['_query'] as $type => $c_val) { 881 switch ($type) { 882 case Horde_Imap_Client::FETCH_FULLMSG: 883 foreach ($seq_ids as $id) { 884 $tmp = $this->_pop3Cache('msg', $id); 885 886 if (empty($c_val['start']) && empty($c_val['length'])) { 887 $tmp2 = fopen('php://temp', 'r+'); 888 stream_copy_to_stream($tmp, $tmp2, empty($c_val['length']) ? -1 : $c_val['length'], empty($c_val['start']) ? 0 : $c_val['start']); 889 $results->get($lookup[$id])->setFullMsg($tmp2); 890 } else { 891 $results->get($lookup[$id])->setFullMsg($tmp); 892 } 893 } 894 break; 895 896 case Horde_Imap_Client::FETCH_HEADERTEXT: 897 // Ignore 'peek' option 898 foreach ($c_val as $key => $val) { 899 foreach ($seq_ids as $id) { 900 /* Message header can be retrieved via TOP, if the 901 * command is available. */ 902 try { 903 $tmp = ($key == 0) 904 ? $this->_pop3Cache('hdr', $id) 905 : Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key); 906 $results->get($lookup[$id])->setHeaderText($key, $this->_processString($tmp, $c_val)); 907 } catch (Horde_Mime_Exception $e) {} 908 } 909 } 910 break; 911 912 case Horde_Imap_Client::FETCH_BODYTEXT: 913 // Ignore 'peek' option 914 foreach ($c_val as $key => $val) { 915 foreach ($seq_ids as $id) { 916 try { 917 $results->get($lookup[$id])->setBodyText($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val)); 918 } catch (Horde_Mime_Exception $e) {} 919 } 920 } 921 break; 922 923 case Horde_Imap_Client::FETCH_MIMEHEADER: 924 // Ignore 'peek' option 925 foreach ($c_val as $key => $val) { 926 foreach ($seq_ids as $id) { 927 try { 928 $results->get($lookup[$id])->setMimeHeader($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key), $val)); 929 } catch (Horde_Mime_Exception $e) {} 930 } 931 } 932 break; 933 934 case Horde_Imap_Client::FETCH_BODYPART: 935 // Ignore 'decode', 'peek' 936 foreach ($c_val as $key => $val) { 937 foreach ($seq_ids as $id) { 938 try { 939 $results->get($lookup[$id])->setBodyPart($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val)); 940 } catch (Horde_Mime_Exception $e) {} 941 } 942 } 943 break; 944 945 case Horde_Imap_Client::FETCH_HEADERS: 946 // Ignore 'length', 'peek' 947 foreach ($seq_ids as $id) { 948 $ob = $this->_pop3Cache('hdrob', $id); 949 foreach ($c_val as $key => $val) { 950 $tmp = $ob; 951 952 if (empty($val['notsearch'])) { 953 $tmp2 = $tmp->toArray(array('nowrap' => true)); 954 foreach (array_keys($tmp2) as $hdr) { 955 if (!in_array($hdr, $val['headers'])) { 956 unset($tmp[$hdr]); 957 } 958 } 959 } else { 960 foreach ($val['headers'] as $hdr) { 961 unset($tmp[$hdr]); 962 } 963 } 964 965 $results->get($lookup[$id])->setHeaders($key, $tmp); 966 } 967 } 968 break; 969 970 case Horde_Imap_Client::FETCH_STRUCTURE: 971 foreach ($seq_ids as $id) { 972 if ($ptr = $this->_pop3Cache('msg', $id)) { 973 try { 974 $results->get($lookup[$id])->setStructure(Horde_Mime_Part::parseMessage(stream_get_contents($ptr), array('no_body' => true))); 975 } catch (Horde_Exception $e) {} 976 } 977 } 978 break; 979 980 case Horde_Imap_Client::FETCH_ENVELOPE: 981 foreach ($seq_ids as $id) { 982 $tmp = $this->_pop3Cache('hdrob', $id); 983 $results->get($lookup[$id])->setEnvelope(array( 984 'date' => $tmp['Date'], 985 'subject' => $tmp['Subject'], 986 'from' => ($h = $tmp['From']) ? $h->getAddressList(true) : null, 987 'sender' => ($h = $tmp['Sender']) ? $h->getAddressList(true) : null, 988 'reply_to' => ($h = $tmp['Reply-to']) ? $h->getAddressList(true) : null, 989 'to' => ($h = $tmp['To']) ? $h->getAddressList(true) : null, 990 'cc' => ($h = $tmp['Cc']) ? $h->getAddressList(true) : null, 991 'bcc' => ($h = $tmp['Bcc']) ? $h->getAddressList(true) : null, 992 'in_reply_to' => $tmp['In-Reply-To'], 993 'message_id' => $tmp['Message-ID'] 994 )); 995 } 996 break; 997 998 case Horde_Imap_Client::FETCH_IMAPDATE: 999 foreach ($seq_ids as $id) { 1000 $tmp = $this->_pop3Cache('hdrob', $id); 1001 $results->get($lookup[$id])->setImapDate($tmp['Date']); 1002 } 1003 break; 1004 1005 case Horde_Imap_Client::FETCH_SIZE: 1006 $sizelist = $this->_pop3Cache('size'); 1007 foreach ($seq_ids as $id) { 1008 $results->get($lookup[$id])->setSize($sizelist[$id]); 1009 } 1010 break; 1011 1012 case Horde_Imap_Client::FETCH_SEQ: 1013 foreach ($seq_ids as $id) { 1014 $results->get($lookup[$id])->setSeq($id); 1015 } 1016 break; 1017 1018 case Horde_Imap_Client::FETCH_UID: 1019 $uidllist = $this->_pop3Cache('uidl'); 1020 foreach ($seq_ids as $id) { 1021 if (isset($uidllist[$id])) { 1022 $results->get($lookup[$id])->setUid($uidllist[$id]); 1023 } 1024 } 1025 break; 1026 } 1027 } 1028 } 1029 1030 /** 1031 * Retrieve locally cached message data. 1032 * 1033 * @param string $type Either 'hdr', 'hdrob', 'msg', 'size', 'stat', 1034 * 'top', or 'uidl'. 1035 * @param integer $index The message index. 1036 * @param mixed $data Additional information needed. 1037 * 1038 * @return mixed The cached data. 'msg' returns a stream resource. All 1039 * other types return strings. 1040 * 1041 * @throws Horde_Imap_Client_Exception 1042 */ 1043 protected function _pop3Cache( 1044 $type, $index = self::MBOX_CACHE, $data = null 1045 ) 1046 { 1047 if (isset($this->_temp['pop3cache'][$index][$type])) { 1048 if ($type == 'msg') { 1049 rewind($this->_temp['pop3cache'][$index][$type]); 1050 } 1051 return $this->_temp['pop3cache'][$index][$type]; 1052 } 1053 1054 switch ($type) { 1055 case 'hdr': 1056 case 'top': 1057 $data = null; 1058 if (($type == 'top') || $this->_capability('TOP')) { 1059 try { 1060 $res = $this->_sendLine('TOP ' . $index . ' 0', array( 1061 'multiline' => 'stream' 1062 )); 1063 rewind($res['data']); 1064 $data = stream_get_contents($res['data']); 1065 fclose($res['data']); 1066 } catch (Horde_Imap_Client_Exception $e) { 1067 $this->_temp['no_top'] = true; 1068 if ($type == 'top') { 1069 return null; 1070 } 1071 } 1072 } 1073 1074 if (is_null($data)) { 1075 $data = Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $index)), 'header', 0); 1076 } 1077 break; 1078 1079 case 'hdrob': 1080 $data = Horde_Mime_Headers::parseHeaders($this->_pop3Cache('hdr', $index)); 1081 break; 1082 1083 case 'msg': 1084 $res = $this->_sendLine('RETR ' . $index, array( 1085 'multiline' => 'stream' 1086 )); 1087 $data = $res['data']; 1088 rewind($data); 1089 break; 1090 1091 case 'size': 1092 case 'uidl': 1093 $data = array(); 1094 try { 1095 $res = $this->_sendLine(($type == 'size') ? 'LIST' : 'UIDL', array( 1096 'multiline' => 'array' 1097 )); 1098 foreach ($res['data'] as $val) { 1099 $resp_data = explode(' ', $val, 2); 1100 $data[$resp_data[0]] = $resp_data[1]; 1101 } 1102 } catch (Horde_Imap_Client_Exception $e) { 1103 if ($type == 'uidl') { 1104 $this->_temp['no_uidl'] = true; 1105 } 1106 } 1107 break; 1108 1109 case 'stat': 1110 $resp = $this->_sendLine('STAT'); 1111 $resp_data = explode(' ', $resp['resp'], 2); 1112 $data = array('msgs' => $resp_data[0], 'size' => $resp_data[1]); 1113 break; 1114 } 1115 1116 $this->_temp['pop3cache'][$index][$type] = $data; 1117 1118 return $data; 1119 } 1120 1121 /** 1122 * Process a string response based on criteria options. 1123 * 1124 * @param string $str The original string. 1125 * @param array $opts The criteria options. 1126 * 1127 * @return string The requested string. 1128 */ 1129 protected function _processString($str, $opts) 1130 { 1131 if (!empty($opts['length'])) { 1132 return substr($str, empty($opts['start']) ? 0 : $opts['start'], $opts['length']); 1133 } elseif (!empty($opts['start'])) { 1134 return substr($str, $opts['start']); 1135 } 1136 1137 return $str; 1138 } 1139 1140 /** 1141 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1142 */ 1143 protected function _vanished($modseq, Horde_Imap_Client_Ids $ids) 1144 { 1145 throw new Horde_Imap_Client_Exception_NoSupportPop3('QRESYNC commands'); 1146 } 1147 1148 /** 1149 * @param array $options Additional options. This driver does not support 1150 * 'unchangedsince'. 1151 */ 1152 protected function _store($options) 1153 { 1154 $delete = $reset = false; 1155 1156 /* Only support deleting/undeleting messages. */ 1157 if (isset($options['replace'])) { 1158 $delete = (bool)(count(array_intersect($options['replace'], array( 1159 Horde_Imap_Client::FLAG_DELETED 1160 )))); 1161 $reset = !$delete; 1162 } else { 1163 if (!empty($options['add'])) { 1164 $delete = (bool)(count(array_intersect($options['add'], array( 1165 Horde_Imap_Client::FLAG_DELETED 1166 )))); 1167 } 1168 1169 if (!empty($options['remove'])) { 1170 $reset = !(bool)(count(array_intersect($options['remove'], array( 1171 Horde_Imap_Client::FLAG_DELETED 1172 )))); 1173 } 1174 } 1175 1176 if ($reset) { 1177 $this->_sendLine('RSET'); 1178 } elseif ($delete) { 1179 foreach ($this->_getSeqIds($options['ids']) as $id) { 1180 try { 1181 $this->_sendLine('DELE ' . $id); 1182 $this->_deleted[] = $id; 1183 1184 unset( 1185 $this->_temp['pop3cache'][self::MBOX_CACHE], 1186 $this->_temp['pop3cache'][$id] 1187 ); 1188 } catch (Horde_Imap_Client_Exception $e) {} 1189 } 1190 } 1191 1192 return $this->getIdsOb(); 1193 } 1194 1195 /** 1196 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1197 */ 1198 protected function _copy(Horde_Imap_Client_Mailbox $dest, $options) 1199 { 1200 throw new Horde_Imap_Client_Exception_NoSupportPop3('Copying messages'); 1201 } 1202 1203 /** 1204 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1205 */ 1206 protected function _setQuota(Horde_Imap_Client_Mailbox $root, $options) 1207 { 1208 throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas'); 1209 } 1210 1211 /** 1212 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1213 */ 1214 protected function _getQuota(Horde_Imap_Client_Mailbox $root) 1215 { 1216 throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas'); 1217 } 1218 1219 /** 1220 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1221 */ 1222 protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox) 1223 { 1224 throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas'); 1225 } 1226 1227 /** 1228 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1229 */ 1230 protected function _setACL(Horde_Imap_Client_Mailbox $mailbox, $identifier, 1231 $options) 1232 { 1233 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1234 } 1235 1236 /** 1237 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1238 */ 1239 protected function _deleteACL(Horde_Imap_Client_Mailbox $mailbox, $identifier) 1240 { 1241 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1242 } 1243 1244 /** 1245 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1246 */ 1247 protected function _getACL(Horde_Imap_Client_Mailbox $mailbox) 1248 { 1249 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1250 } 1251 1252 /** 1253 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1254 */ 1255 protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox, 1256 $identifier) 1257 { 1258 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1259 } 1260 1261 /** 1262 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1263 */ 1264 protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox) 1265 { 1266 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1267 } 1268 1269 /** 1270 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1271 */ 1272 protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox, 1273 $entries, $options) 1274 { 1275 throw new Horde_Imap_Client_Exception_NoSupportPop3('Metadata'); 1276 } 1277 1278 /** 1279 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1280 */ 1281 protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox, $data) 1282 { 1283 throw new Horde_Imap_Client_Exception_NoSupportPop3('Metadata'); 1284 } 1285 1286 /** 1287 */ 1288 protected function _getSearchCache($type, $options) 1289 { 1290 /* POP3 does not support search caching. */ 1291 return null; 1292 } 1293 1294 /** 1295 */ 1296 public function resolveIds(Horde_Imap_Client_Mailbox $mailbox, 1297 Horde_Imap_Client_Ids $ids, $convert = 0) 1298 { 1299 if (!$ids->special && 1300 (!$convert || 1301 (!$ids->sequence && ($convert == 1)) || 1302 $ids->isEmpty())) { 1303 return clone $ids; 1304 } 1305 1306 $uids = $this->_pop3Cache('uidl'); 1307 1308 return $this->getIdsOb( 1309 $ids->all ? array_values($uids) : array_intersect_keys($uids, $ids->ids) 1310 ); 1311 } 1312 1313 /* Internal functions. */ 1314 1315 /** 1316 * Perform a command on the server. A connection to the server must have 1317 * already been made. 1318 * 1319 * @param string $cmd The command to execute. 1320 * @param array $options Additional options: 1321 * <pre> 1322 * - debug: (string) When debugging, send this string instead of the 1323 * actual command/data sent. 1324 * DEFAULT: Raw data output to debug stream. 1325 * - multiline: (mixed) 'array', 'none', or 'stream'. 1326 * </pre> 1327 * 1328 * @return array See _getResponse(). 1329 * 1330 * @throws Horde_Imap_Client_Exception 1331 */ 1332 protected function _sendLine($cmd, $options = array()) 1333 { 1334 if (!empty($options['debug'])) { 1335 $this->_debug->client($options['debug']); 1336 } 1337 1338 if ($this->_debug->debug) { 1339 $timer = new Horde_Support_Timer(); 1340 $timer->push(); 1341 } 1342 1343 try { 1344 $this->_connection->write($cmd, empty($options['debug'])); 1345 } catch (Horde_Imap_Client_Exception $e) { 1346 throw $e; 1347 } 1348 1349 $resp = $this->_getResponse( 1350 empty($options['multiline']) ? false : $options['multiline'] 1351 ); 1352 1353 if ($this->_debug->debug) { 1354 $this->_debug->info(sprintf( 1355 'Command took %s seconds.', 1356 round($timer->pop(), 4) 1357 )); 1358 } 1359 1360 return $resp; 1361 } 1362 1363 /** 1364 * Gets a line from the stream and parses it. 1365 * 1366 * @param mixed $multiline 'array', 'none', 'stream', or null. 1367 * 1368 * @return array An array with the following keys: 1369 * - data: (mixed) Stream, array, or null. 1370 * - resp: (string) The server response text. 1371 * 1372 * @throws Horde_Imap_Client_Exception 1373 */ 1374 protected function _getResponse($multiline = false) 1375 { 1376 $ob = array('resp' => ''); 1377 1378 $read = explode(' ', rtrim($this->_connection->read(), "\r\n"), 2); 1379 if (!in_array($read[0], array('+OK', '-ERR', '+'))) { 1380 $this->_debug->info('ERROR: IMAP read/timeout error.'); 1381 throw new Horde_Imap_Client_Exception( 1382 Horde_Imap_Client_Translation::r("Error when communicating with the mail server."), 1383 Horde_Imap_Client_Exception::SERVER_READERROR 1384 ); 1385 } 1386 1387 $respcode = null; 1388 if (isset($read[1]) && 1389 isset($this->_init['capability']) && 1390 $this->_capability('RESP-CODES')) { 1391 $respcode = $this->_parseResponseCode($read[1]); 1392 } 1393 1394 switch ($read[0]) { 1395 case '+OK': 1396 case '+': 1397 if ($respcode) { 1398 $ob['resp'] = $respcode->text; 1399 } elseif (isset($read[1])) { 1400 $ob['resp'] = $read[1]; 1401 } 1402 break; 1403 1404 case '-ERR': 1405 $errcode = 0; 1406 if ($respcode) { 1407 $errtext = $respcode->text; 1408 1409 if (isset($respcode->code)) { 1410 switch ($respcode->code) { 1411 // RFC 2449 [8.1.1] 1412 case 'IN-USE': 1413 // RFC 2449 [8.1.2] 1414 case 'LOGIN-DELAY': 1415 $errcode = Horde_Imap_Client_Exception::LOGIN_UNAVAILABLE; 1416 break; 1417 1418 // RFC 3206 [4] 1419 case 'SYS/TEMP': 1420 $errcode = Horde_Imap_Client_Exception::POP3_TEMP_ERROR; 1421 break; 1422 1423 // RFC 3206 [4] 1424 case 'SYS/PERM': 1425 $errcode = Horde_Imap_Client_Exception::POP3_PERM_ERROR; 1426 break; 1427 1428 // RFC 3206 [5] 1429 case 'AUTH': 1430 $errcode = Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED; 1431 break; 1432 1433 // RFC 6856 [5] 1434 case 'UTF8': 1435 /* This code can only be issued if we (as client) are 1436 * broken, so no need to handle since we should never 1437 * be broken. */ 1438 break; 1439 } 1440 } 1441 } elseif (isset($read[1])) { 1442 $errtext = $read[1]; 1443 } else { 1444 $errtext = '[No error message provided by server]'; 1445 } 1446 1447 $e = new Horde_Imap_Client_Exception( 1448 Horde_Imap_Client_Translation::r("POP3 error reported by server."), 1449 $errcode 1450 ); 1451 $e->details = $errtext; 1452 throw $e; 1453 } 1454 1455 switch ($multiline) { 1456 case 'array': 1457 $ob['data'] = array(); 1458 break; 1459 1460 case 'none': 1461 $ob['data'] = null; 1462 break; 1463 1464 case 'stream': 1465 $ob['data'] = fopen('php://temp', 'r+'); 1466 break; 1467 1468 default: 1469 return $ob; 1470 } 1471 1472 do { 1473 $orig_read = $this->_connection->read(); 1474 $read = rtrim($orig_read, "\r\n"); 1475 1476 if ($read === '.') { 1477 break; 1478 } elseif (substr($read, 0, 2) === '..') { 1479 $read = substr($read, 1); 1480 } 1481 1482 if (is_array($ob['data'])) { 1483 $ob['data'][] = $read; 1484 } elseif (!is_null($ob['data'])) { 1485 fwrite($ob['data'], $orig_read); 1486 } 1487 } while (true); 1488 1489 return $ob; 1490 } 1491 1492 /** 1493 * Returns a list of sequence IDs. 1494 * 1495 * @param Horde_Imap_Client_Ids $ids The ID list. 1496 * 1497 * @return array A list of sequence IDs. 1498 */ 1499 protected function _getSeqIds(Horde_Imap_Client_Ids $ids) 1500 { 1501 if (!count($ids)) { 1502 $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES); 1503 return range(1, $status['messages']); 1504 } elseif ($ids->sequence) { 1505 return $ids->ids; 1506 } 1507 1508 return array_keys(array_intersect($this->_pop3Cache('uidl'), $ids->ids)); 1509 } 1510 1511 /** 1512 * Parses response text for response codes (RFC 2449 [8]). 1513 * 1514 * @param string $text The response text. 1515 * 1516 * @return object An object with the following properties: 1517 * - code: (string) The response code, if it exists. 1518 * - data: (string) The response code data, if it exists. 1519 * - text: (string) The human-readable response text. 1520 */ 1521 protected function _parseResponseCode($text) 1522 { 1523 $ret = new stdClass; 1524 1525 $text = trim($text); 1526 if ($text[0] === '[') { 1527 $pos = strpos($text, ' ', 2); 1528 $end_pos = strpos($text, ']', 2); 1529 if ($pos > $end_pos) { 1530 $ret->code = Horde_String::upper(substr($text, 1, $end_pos - 1)); 1531 } else { 1532 $ret->code = Horde_String::upper(substr($text, 1, $pos - 1)); 1533 $ret->data = substr($text, $pos + 1, $end_pos - $pos - 1); 1534 } 1535 $ret->text = trim(substr($text, $end_pos + 1)); 1536 } else { 1537 $ret->text = $text; 1538 } 1539 1540 return $ret; 1541 } 1542 1543 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body