[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Copyright 2009-2014 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING 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-2014 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 * </pre> 68 * 69 * @author Richard Heyes <richard@phpguru.org> 70 * @author Michael Slusarz <slusarz@horde.org> 71 * @category Horde 72 * @copyright 2002 Richard Heyes 73 * @copyright 2009-2014 Horde LLC 74 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 75 * @package Imap_Client 76 */ 77 class Horde_Imap_Client_Socket_Pop3 extends Horde_Imap_Client_Base 78 { 79 /** 80 * The default ports to use for a connection. 81 * 82 * @var array 83 */ 84 protected $_defaultPorts = array(110, 995); 85 86 /** 87 * The list of deleted messages. 88 * 89 * @var array 90 */ 91 protected $_deleted = array(); 92 93 /** 94 * This object returns POP3 Fetch data objects. 95 * 96 * @var string 97 */ 98 protected $_fetchDataClass = 'Horde_Imap_Client_Data_Fetch_Pop3'; 99 100 /** 101 */ 102 protected function _initCache($current = false) 103 { 104 return parent::_initCache($current) && 105 $this->queryCapability('UIDL'); 106 } 107 108 /** 109 */ 110 public function getIdsOb($ids = null, $sequence = false) 111 { 112 return new Horde_Imap_Client_Ids_Pop3($ids, $sequence); 113 } 114 115 /** 116 */ 117 protected function _capability() 118 { 119 $this->_connect(); 120 121 $capability = array(); 122 123 try { 124 $res = $this->_sendLine('CAPA', array( 125 'multiline' => 'array' 126 )); 127 128 foreach ($res['data'] as $val) { 129 $prefix = explode(' ', $val); 130 $capability[strtoupper($prefix[0])] = (count($prefix) > 1) 131 ? array_slice($prefix, 1) 132 : true; 133 } 134 } catch (Horde_Imap_Client_Exception $e) { 135 $this->_temp['no_capa'] = true; 136 137 /* Need to probe for capabilities if CAPA command is not 138 * available. */ 139 $capability = array('USER' => true); 140 141 /* Capability sniffing only guaranteed after authentication is 142 * completed (if any). */ 143 if (!empty($this->_init['authmethod'])) { 144 $this->_pop3Cache('uidl'); 145 if (empty($this->_temp['no_uidl'])) { 146 $capability['UIDL'] = true; 147 } 148 149 $this->_pop3Cache('top', 1); 150 if (empty($this->_temp['no_top'])) { 151 $capability['TOP'] = true; 152 } 153 } 154 } 155 156 $this->_setInit('capability', $capability); 157 } 158 159 /** 160 */ 161 protected function _noop() 162 { 163 $this->_sendLine('NOOP'); 164 } 165 166 /** 167 * @throws Horde_Imap_Client_Exception_NoSupportPop3 168 */ 169 protected function _getNamespaces() 170 { 171 throw new Horde_Imap_Client_Exception_NoSupportPop3('Namespaces'); 172 } 173 174 /** 175 */ 176 public function alerts() 177 { 178 return array(); 179 } 180 181 /** 182 */ 183 protected function _login() 184 { 185 /* Blank passwords are not allowed, so no need to even try 186 * authentication to determine this. */ 187 if (is_null($this->getParam('password'))) { 188 throw new Horde_Imap_Client_Exception( 189 Horde_Imap_Client_Translation::r("No password provided."), 190 Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED 191 ); 192 } 193 194 $this->_connect(); 195 196 $first_login = empty($this->_init['authmethod']); 197 198 // Switch to secure channel if using TLS. 199 if (!$this->isSecureConnection()) { 200 $secure = $this->getParam('secure'); 201 202 if (($secure === 'tls') || $secure === true) { 203 // Switch over to a TLS connection. 204 if ($first_login && !$this->queryCapability('STLS')) { 205 if ($secure === 'tls') { 206 throw new Horde_Imap_Client_Exception( 207 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."), 208 Horde_Imap_Client_Exception::LOGIN_TLSFAILURE 209 ); 210 } else { 211 $this->setParam('secure', false); 212 } 213 } else { 214 $this->_sendLine('STLS'); 215 216 $this->setParam('secure', 'tls'); 217 218 if (!$this->_connection->startTls()) { 219 $this->logout(); 220 throw new Horde_Imap_Client_Exception( 221 Horde_Imap_Client_Translation::r("Could not open secure connection to the POP3 server."), 222 Horde_Imap_Client_Exception::LOGIN_TLSFAILURE 223 ); 224 } 225 $this->_debug->info('Successfully completed TLS negotiation.'); 226 } 227 228 // Expire cached CAPABILITY information 229 $this->_setInit('capability'); 230 } else { 231 $this->setParam('secure', false); 232 } 233 } 234 235 if ($first_login) { 236 /* Sanity checking: at least one server (Dovecot 1.x) may return 237 * SASL response with no arguments. */ 238 $auth_mech = (($sasl = $this->queryCapability('SASL')) && is_array($sasl)) 239 ? $sasl 240 : array(); 241 242 if (isset($this->_temp['pop3timestamp'])) { 243 $auth_mech[] = 'APOP'; 244 } 245 246 $auth_mech[] = 'USER'; 247 } else { 248 $auth_mech = array($this->_init['authmethod']); 249 } 250 251 foreach ($auth_mech as $method) { 252 try { 253 $this->_tryLogin($method); 254 $this->_setInit('authmethod', $method); 255 256 if (!empty($this->_temp['no_capa']) || 257 !$this->queryCapability('UIDL')) { 258 $this->_capability(); 259 } 260 261 return true; 262 } catch (Horde_Imap_Client_Exception $e) { 263 if (!empty($this->_init['authmethod']) && 264 ($e->getCode() != $e::LOGIN_UNAVAILABLE) && 265 ($e->getCode() != $e::POP3_TEMP_ERROR)) { 266 $this->_setInit(); 267 return $this->login(); 268 } 269 } 270 } 271 272 throw new Horde_Imap_Client_Exception( 273 Horde_Imap_Client_Translation::r("POP3 server denied authentication."), 274 $e->getCode() ?: $e::LOGIN_AUTHENTICATIONFAILED 275 ); 276 } 277 278 /** 279 * Connects to the server. 280 * 281 * @throws Horde_Imap_Client_Exception 282 */ 283 protected function _connect() 284 { 285 if (!is_null($this->_connection)) { 286 return; 287 } 288 289 try { 290 $this->_connection = new Horde_Imap_Client_Socket_Connection_Pop3( 291 $this->getParam('hostspec'), 292 $this->getParam('port'), 293 $this->getParam('timeout'), 294 $this->getParam('secure'), 295 array( 296 'debug' => $this->_debug 297 ) 298 ); 299 } catch (Horde\Socket\Client\Exception $e) { 300 $e2 = new Horde_Imap_Client_Exception( 301 Horde_Imap_Client_Translation::r("Error connecting to mail server."), 302 Horde_Imap_Client_Exception::SERVER_CONNECT 303 ); 304 $e2->details = $e->details; 305 throw $e2; 306 } 307 308 $line = $this->_getResponse(); 309 310 // Check for string matching APOP timestamp 311 if (preg_match('/<.+@.+>/U', $line['resp'], $matches)) { 312 $this->_temp['pop3timestamp'] = $matches[0]; 313 } 314 } 315 316 /** 317 * Authenticate to the POP3 server. 318 * 319 * @param string $method POP3 login method. 320 * 321 * @throws Horde_Imap_Client_Exception 322 */ 323 protected function _tryLogin($method) 324 { 325 $username = $this->getParam('username'); 326 $password = $this->getParam('password'); 327 328 switch ($method) { 329 case 'CRAM-MD5': 330 case 'CRAM-SHA1': 331 case 'CRAM-SHA256': 332 // RFC 5034: CRAM-MD5 333 // CRAM-SHA1 & CRAM-SHA256 supported by Courier SASL library 334 $challenge = $this->_sendLine('AUTH ' . $method); 335 $response = base64_encode($username . ' ' . hash_hmac(strtolower(substr($method, 5)), base64_decode(substr($challenge['resp'], 2)), $password, true)); 336 $this->_sendLine($response, array( 337 'debug' => sprintf('[%s Response - username: %s]', $method, $username) 338 )); 339 break; 340 341 case 'DIGEST-MD5': 342 // RFC 2831; Obsoleted by RFC 6331 343 $challenge = $this->_sendLine('AUTH DIGEST-MD5'); 344 $response = base64_encode(new Horde_Imap_Client_Auth_DigestMD5( 345 $username, 346 $password, 347 base64_decode(substr($challenge['resp'], 2)), 348 $this->getParam('hostspec'), 349 'pop3' 350 )); 351 $sresponse = $this->_sendLine($response, array( 352 'debug' => sprintf('[%s Response - username: %s]', $method, $username) 353 )); 354 if (stripos(base64_decode(substr($sresponse['resp'], 2)), 'rspauth=') === false) { 355 throw new Horde_Imap_Client_Exception( 356 Horde_Imap_Client_Translation::r("Unexpected response from server when authenticating."), 357 Horde_Imap_Client_Exception::SERVER_CONNECT 358 ); 359 } 360 361 /* POP3 doesn't use protocol's third step. */ 362 $this->_sendLine(''); 363 break; 364 365 case 'LOGIN': 366 // RFC 4616 (AUTH=PLAIN) & 5034 (POP3 SASL) 367 $this->_sendLine('AUTH LOGIN'); 368 $this->_sendLine(base64_encode($username), array( 369 'debug' => sprintf('[AUTH LOGIN Command - username: %s]', $username) 370 )); 371 $this->_sendLine(base64_encode($password), array( 372 'debug' => '[AUTH LOGIN Command - password]' 373 )); 374 break; 375 376 case 'PLAIN': 377 // RFC 5034 378 $this->_sendLine('AUTH PLAIN ' . base64_encode(implode("\0", array( 379 $username, 380 $username, 381 $password 382 ))), array( 383 'debug' => sprintf('[AUTH PLAIN Command - username: %s]', $username) 384 )); 385 break; 386 387 case 'APOP': 388 // RFC 1939 [7] 389 $this->_sendLine('APOP ' . $username . ' ' . hash('md5', $this->_temp['pop3timestamp'] . $password)); 390 break; 391 392 case 'USER': 393 // RFC 1939 [7] 394 $this->_sendLine('USER ' . $username); 395 $this->_sendLine('PASS ' . $password, array( 396 'debug' => '[USER Command - password]' 397 )); 398 break; 399 400 default: 401 throw new Horde_Imap_Client_Exception( 402 sprintf(Horde_Imap_Client_Translation::r("Unknown authentication method: %s"), $method), 403 Horde_Imap_Client_Exception::SERVER_CONNECT 404 ); 405 } 406 } 407 408 /** 409 */ 410 protected function _logout() 411 { 412 try { 413 $this->_sendLine('QUIT'); 414 } catch (Horde_Imap_Client_Exception $e) {} 415 $this->_deleted = array(); 416 } 417 418 /** 419 * @throws Horde_Imap_Client_Exception_NoSupportPop3 420 */ 421 protected function _sendID($info) 422 { 423 throw new Horde_Imap_Client_Exception_NoSupportPop3('ID command'); 424 } 425 426 /** 427 * Return implementation information from the POP3 server (RFC 2449 [6.9]). 428 */ 429 protected function _getID() 430 { 431 $id = $this->queryCapability('IMPLEMENTATION'); 432 return empty($id) 433 ? array() 434 : array('implementation' => $id); 435 } 436 437 /** 438 * @throws Horde_Imap_Client_Exception_NoSupportPop3 439 */ 440 protected function _setLanguage($langs) 441 { 442 throw new Horde_Imap_Client_Exception_NoSupportPop3('LANGUAGE extension'); 443 } 444 445 /** 446 * @throws Horde_Imap_Client_Exception_NoSupportPop3 447 */ 448 protected function _getLanguage($list) 449 { 450 throw new Horde_Imap_Client_Exception_NoSupportPop3('LANGUAGE extension'); 451 } 452 453 /** 454 * @throws Horde_Imap_Client_Exception_NoSupportPop3 455 */ 456 protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox, $mode) 457 { 458 if ($mailbox != 'INBOX') { 459 throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX'); 460 } 461 $this->_changeSelected($mailbox, $mode); 462 } 463 464 /** 465 * @throws Horde_Imap_Client_Exception_NoSupportPop3 466 */ 467 protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox, $opts) 468 { 469 throw new Horde_Imap_Client_Exception_NoSupportPop3('Creating mailboxes'); 470 } 471 472 /** 473 * @throws Horde_Imap_Client_Exception_NoSupportPop3 474 */ 475 protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox) 476 { 477 throw new Horde_Imap_Client_Exception_NoSupportPop3('Deleting mailboxes'); 478 } 479 480 /** 481 * @throws Horde_Imap_Client_Exception_NoSupportPop3 482 */ 483 protected function _renameMailbox(Horde_Imap_Client_Mailbox $old, 484 Horde_Imap_Client_Mailbox $new) 485 { 486 throw new Horde_Imap_Client_Exception_NoSupportPop3('Renaming mailboxes'); 487 } 488 489 /** 490 * @throws Horde_Imap_Client_Exception_NoSupportPop3 491 */ 492 protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox, 493 $subscribe) 494 { 495 throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX'); 496 } 497 498 /** 499 */ 500 protected function _listMailboxes($pattern, $mode, $options) 501 { 502 $tmp = array( 503 'mailbox' => Horde_Imap_Client_Mailbox::get('INBOX') 504 ); 505 506 if (!empty($options['attributes'])) { 507 $tmp['attributes'] = array(); 508 } 509 if (!empty($options['delimiter'])) { 510 $tmp['delimiter'] = ''; 511 } 512 513 return array('INBOX' => $tmp); 514 } 515 516 /** 517 * @param integer $flags This driver only supports the options listed 518 * under Horde_Imap_Client::STATUS_ALL. 519 * @throws Horde_Imap_Client_Exception_NoSupportPop3 520 */ 521 protected function _status($mboxes, $flags) 522 { 523 if ((count($mboxes) > 1) || (reset($mboxes) != 'INBOX')) { 524 throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX'); 525 } 526 527 $this->openMailbox('INBOX'); 528 529 $ret = array(); 530 531 if ($flags & Horde_Imap_Client::STATUS_MESSAGES) { 532 $res = $this->_pop3Cache('stat'); 533 $ret['messages'] = $res['msgs']; 534 } 535 536 if ($flags & Horde_Imap_Client::STATUS_RECENT) { 537 $res = $this->_pop3Cache('stat'); 538 $ret['recent'] = $res['msgs']; 539 } 540 541 // No need for STATUS_UIDNEXT_FORCE handling since STATUS_UIDNEXT will 542 // always return a value. 543 if ($flags & Horde_Imap_Client::STATUS_UIDNEXT) { 544 $res = $this->_pop3Cache('stat'); 545 $ret['uidnext'] = $res['msgs'] + 1; 546 } 547 548 if ($flags & Horde_Imap_Client::STATUS_UIDVALIDITY) { 549 $ret['uidvalidity'] = $this->queryCapability('UIDL') 550 ? 1 551 : microtime(true); 552 } 553 554 if ($flags & Horde_Imap_Client::STATUS_UNSEEN) { 555 $ret['unseen'] = 0; 556 } 557 558 return array('INBOX' => $ret); 559 } 560 561 /** 562 * @throws Horde_Imap_Client_Exception_NoSupportPop3 563 */ 564 protected function _append(Horde_Imap_Client_Mailbox $mailbox, $data, 565 $options) 566 { 567 throw new Horde_Imap_Client_Exception_NoSupportPop3('Appending messages'); 568 } 569 570 /** 571 */ 572 protected function _check() 573 { 574 $this->noop(); 575 } 576 577 /** 578 */ 579 protected function _close($options) 580 { 581 if (!empty($options['expunge'])) { 582 $this->logout(); 583 } 584 } 585 586 /** 587 * @param array $options Additional options. 'ids' has no effect in this 588 * driver. 589 */ 590 protected function _expunge($options) 591 { 592 $msg_list = $this->_deleted; 593 $this->logout(); 594 return empty($options['list']) 595 ? null 596 : $msg_list; 597 } 598 599 /** 600 * @throws Horde_Imap_Client_Exception_NoSupportPop3 601 */ 602 protected function _search($query, $options) 603 { 604 $sort = empty($options['sort']) 605 ? null 606 : reset($options['sort']); 607 608 // Only support a single query: an ALL search sorted by sequence. 609 if ((strval($options['_query']['query']) != 'ALL') || 610 ($sort && 611 ((count($options['sort']) > 1) || 612 ($sort != Horde_Imap_Client::SORT_SEQUENCE)))) { 613 throw new Horde_Imap_Client_Exception_NoSupportPop3('Server search'); 614 } 615 616 $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES); 617 $res = range(1, $status['messages']); 618 619 if (empty($options['sequence'])) { 620 $tmp = array(); 621 $uidllist = $this->_pop3Cache('uidl'); 622 foreach ($res as $val) { 623 $tmp[] = $uidllist[$val]; 624 } 625 $res = $tmp; 626 } 627 628 if (!empty($options['partial'])) { 629 $partial = $this->getIdsOb($options['partial'], true); 630 $min = $partial->min - 1; 631 $res = array_slice($res, $min, $partial->max - $min); 632 } 633 634 $ret = array(); 635 foreach ($options['results'] as $val) { 636 switch ($val) { 637 case Horde_Imap_Client::SEARCH_RESULTS_COUNT: 638 $ret['count'] = count($res); 639 break; 640 641 case Horde_Imap_Client::SEARCH_RESULTS_MATCH: 642 $ret['match'] = $this->getIdsOb($res); 643 break; 644 645 case Horde_Imap_Client::SEARCH_RESULTS_MAX: 646 $ret['max'] = empty($res) ? null : max($res); 647 break; 648 649 case Horde_Imap_Client::SEARCH_RESULTS_MIN: 650 $ret['min'] = empty($res) ? null : min($res); 651 break; 652 } 653 } 654 655 return $ret; 656 } 657 658 /** 659 * @throws Horde_Imap_Client_Exception_NoSupportPop3 660 */ 661 protected function _setComparator($comparator) 662 { 663 throw new Horde_Imap_Client_Exception_NoSupportPop3('Search comparators'); 664 } 665 666 /** 667 * @throws Horde_Imap_Client_Exception_NoSupportPop3 668 */ 669 protected function _getComparator() 670 { 671 throw new Horde_Imap_Client_Exception_NoSupportPop3('Search comparators'); 672 } 673 674 /** 675 * @throws Horde_Imap_Client_Exception_NoSupportPop3 676 */ 677 protected function _thread($options) 678 { 679 throw new Horde_Imap_Client_Exception_NoSupportPop3('Server threading'); 680 } 681 682 /** 683 */ 684 protected function _fetch(Horde_Imap_Client_Fetch_Results $results, 685 $queries) 686 { 687 foreach ($queries as $options) { 688 $this->_fetchCmd($results, $options); 689 } 690 691 $this->_updateCache($results); 692 } 693 694 /** 695 * Fetch data for a given fetch query. 696 * 697 * @param Horde_Imap_Client_Fetch_Results $results Fetch results. 698 * @param array $options Fetch query options. 699 */ 700 protected function _fetchCmd(Horde_Imap_Client_Fetch_Results $results, 701 $options) 702 { 703 // Grab sequence IDs - IDs will always be the message number for 704 // POP3 fetch commands. 705 $seq_ids = $this->_getSeqIds($options['ids']); 706 if (empty($seq_ids)) { 707 return; 708 } 709 710 $lookup = $options['ids']->sequence 711 ? array_combine($seq_ids, $seq_ids) 712 : $this->_pop3Cache('uidl'); 713 714 foreach ($options['_query'] as $type => $c_val) { 715 switch ($type) { 716 case Horde_Imap_Client::FETCH_FULLMSG: 717 foreach ($seq_ids as $id) { 718 $tmp = $this->_pop3Cache('msg', $id); 719 720 if (empty($c_val['start']) && empty($c_val['length'])) { 721 $tmp2 = fopen('php://temp', 'r+'); 722 stream_copy_to_stream($tmp, $tmp2, empty($c_val['length']) ? -1 : $c_val['length'], empty($c_val['start']) ? 0 : $c_val['start']); 723 $results->get($lookup[$id])->setFullMsg($tmp2); 724 } else { 725 $results->get($lookup[$id])->setFullMsg($tmp); 726 } 727 } 728 break; 729 730 case Horde_Imap_Client::FETCH_HEADERTEXT: 731 // Ignore 'peek' option 732 foreach ($c_val as $key => $val) { 733 foreach ($seq_ids as $id) { 734 /* Message header can be retrieved via TOP, if the 735 * command is available. */ 736 try { 737 $tmp = ($key == 0) 738 ? $this->_pop3Cache('hdr', $id) 739 : Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key); 740 $results->get($lookup[$id])->setHeaderText($key, $this->_processString($tmp, $c_val)); 741 } catch (Horde_Mime_Exception $e) {} 742 } 743 } 744 break; 745 746 case Horde_Imap_Client::FETCH_BODYTEXT: 747 // Ignore 'peek' option 748 foreach ($c_val as $key => $val) { 749 foreach ($seq_ids as $id) { 750 try { 751 $results->get($lookup[$id])->setBodyText($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val)); 752 } catch (Horde_Mime_Exception $e) {} 753 } 754 } 755 break; 756 757 case Horde_Imap_Client::FETCH_MIMEHEADER: 758 // Ignore 'peek' option 759 foreach ($c_val as $key => $val) { 760 foreach ($seq_ids as $id) { 761 try { 762 $results->get($lookup[$id])->setMimeHeader($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key), $val)); 763 } catch (Horde_Mime_Exception $e) {} 764 } 765 } 766 break; 767 768 case Horde_Imap_Client::FETCH_BODYPART: 769 // Ignore 'decode', 'peek' 770 foreach ($c_val as $key => $val) { 771 foreach ($seq_ids as $id) { 772 try { 773 $results->get($lookup[$id])->setBodyPart($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val)); 774 } catch (Horde_Mime_Exception $e) {} 775 } 776 } 777 break; 778 779 case Horde_Imap_Client::FETCH_HEADERS: 780 // Ignore 'length', 'peek' 781 foreach ($seq_ids as $id) { 782 $ob = $this->_pop3Cache('hdrob', $id); 783 foreach ($c_val as $key => $val) { 784 $tmp = $ob; 785 786 if (empty($val['notsearch'])) { 787 $tmp2 = $tmp->toArray(array('nowrap' => true)); 788 foreach (array_keys($tmp2) as $hdr) { 789 if (!in_array($hdr, $val['headers'])) { 790 $tmp->removeHeader($hdr); 791 } 792 } 793 } else { 794 foreach ($val['headers'] as $hdr) { 795 $tmp->removeHeader($hdr); 796 } 797 } 798 799 $results->get($lookup[$id])->setHeaders($key, $tmp); 800 } 801 } 802 break; 803 804 case Horde_Imap_Client::FETCH_STRUCTURE: 805 foreach ($seq_ids as $id) { 806 if ($ptr = $this->_pop3Cache('msg', $id)) { 807 try { 808 $results->get($lookup[$id])->setStructure(Horde_Mime_Part::parseMessage(stream_get_contents($ptr), array('no_body' => true))); 809 } catch (Horde_Exception $e) {} 810 } 811 } 812 break; 813 814 case Horde_Imap_Client::FETCH_ENVELOPE: 815 foreach ($seq_ids as $id) { 816 $tmp = $this->_pop3Cache('hdrob', $id); 817 $results->get($lookup[$id])->setEnvelope(array( 818 'date' => $tmp->getValue('date'), 819 'subject' => $tmp->getValue('subject'), 820 'from' => $tmp->getOb('from'), 821 'sender' => $tmp->getOb('sender'), 822 'reply_to' => $tmp->getOb('reply-to'), 823 'to' => $tmp->getOb('to'), 824 'cc' => $tmp->getOb('cc'), 825 'bcc' => $tmp->getOb('bcc'), 826 'in_reply_to' => $tmp->getValue('in-reply-to'), 827 'message_id' => $tmp->getValue('message-id') 828 )); 829 } 830 break; 831 832 case Horde_Imap_Client::FETCH_IMAPDATE: 833 foreach ($seq_ids as $id) { 834 $tmp = $this->_pop3Cache('hdrob', $id); 835 $results->get($lookup[$id])->setImapDate($tmp->getValue('date')); 836 } 837 break; 838 839 case Horde_Imap_Client::FETCH_SIZE: 840 $sizelist = $this->_pop3Cache('size'); 841 foreach ($seq_ids as $id) { 842 $results->get($lookup[$id])->setSize($sizelist[$id]); 843 } 844 break; 845 846 case Horde_Imap_Client::FETCH_SEQ: 847 foreach ($seq_ids as $id) { 848 $results->get($lookup[$id])->setSeq($id); 849 } 850 break; 851 852 case Horde_Imap_Client::FETCH_UID: 853 $uidllist = $this->_pop3Cache('uidl'); 854 foreach ($seq_ids as $id) { 855 if (isset($uidllist[$id])) { 856 $results->get($lookup[$id])->setUid($uidllist[$id]); 857 } 858 } 859 break; 860 } 861 } 862 } 863 864 /** 865 * Retrieve locally cached message data. 866 * 867 * @param string $type Either 'hdr', 'hdrob', 'msg', 'size', 'stat', 868 * 'top', or 'uidl'. 869 * @param integer $index The message index. 870 * @param mixed $data Additional information needed. 871 * 872 * @return mixed The cached data. 'msg' returns a stream resource. All 873 * other types return strings. 874 * 875 * @throws Horde_Imap_Client_Exception 876 */ 877 protected function _pop3Cache($type, $index = null, $data = null) 878 { 879 if (isset($this->_temp['pop3cache'][$index][$type])) { 880 if ($type == 'msg') { 881 rewind($this->_temp['pop3cache'][$index][$type]); 882 } 883 return $this->_temp['pop3cache'][$index][$type]; 884 } 885 886 switch ($type) { 887 case 'hdr': 888 case 'top': 889 $data = null; 890 if ($this->queryCapability('TOP') || ($type == 'top')) { 891 try { 892 $res = $this->_sendLine('TOP ' . $index . ' 0', array( 893 'multiline' => 'stream' 894 )); 895 rewind($res['data']); 896 $data = stream_get_contents($res['data']); 897 fclose($res['data']); 898 } catch (Horde_Imap_Client_Exception $e) { 899 $this->_temp['no_top'] = true; 900 if ($type == 'top') { 901 return null; 902 } 903 } 904 } 905 906 if (is_null($data)) { 907 $data = Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $index)), 'header', 0); 908 } 909 break; 910 911 case 'hdrob': 912 $data = Horde_Mime_Headers::parseHeaders($this->_pop3Cache('hdr', $index)); 913 break; 914 915 case 'msg': 916 $res = $this->_sendLine('RETR ' . $index, array( 917 'multiline' => 'stream' 918 )); 919 $data = $res['data']; 920 rewind($data); 921 break; 922 923 case 'size': 924 case 'uidl': 925 $data = array(); 926 try { 927 $res = $this->_sendLine(($type == 'size') ? 'LIST' : 'UIDL', array( 928 'multiline' => 'array' 929 )); 930 foreach ($res['data'] as $val) { 931 $resp_data = explode(' ', $val, 2); 932 $data[$resp_data[0]] = $resp_data[1]; 933 } 934 } catch (Horde_Imap_Client_Exception $e) { 935 if ($type == 'uidl') { 936 $this->_temp['no_uidl'] = true; 937 } 938 } 939 break; 940 941 case 'stat': 942 $resp = $this->_sendLine('STAT'); 943 $resp_data = explode(' ', $resp['resp'], 2); 944 $data = array('msgs' => $resp_data[0], 'size' => $resp_data[1]); 945 break; 946 } 947 948 $this->_temp['pop3cache'][$index][$type] = $data; 949 950 return $data; 951 } 952 953 /** 954 * Process a string response based on criteria options. 955 * 956 * @param string $str The original string. 957 * @param array $opts The criteria options. 958 * 959 * @return string The requested string. 960 */ 961 protected function _processString($str, $opts) 962 { 963 if (!empty($opts['length'])) { 964 return substr($str, empty($opts['start']) ? 0 : $opts['start'], $opts['length']); 965 } elseif (!empty($opts['start'])) { 966 return substr($str, $opts['start']); 967 } 968 969 return $str; 970 } 971 972 /** 973 * @throws Horde_Imap_Client_Exception_NoSupportPop3 974 */ 975 protected function _vanished($modseq, Horde_Imap_Client_Ids $ids) 976 { 977 throw new Horde_Imap_Client_Exception_NoSupportPop3('QRESYNC commands'); 978 } 979 980 /** 981 * @param array $options Additional options. This driver does not support 982 * 'unchangedsince'. 983 */ 984 protected function _store($options) 985 { 986 $delete = $reset = false; 987 988 /* Only support deleting/undeleting messages. */ 989 if (isset($options['replace'])) { 990 $delete = (bool)(count(array_intersect($options['replace'], array( 991 Horde_Imap_Client::FLAG_DELETED 992 )))); 993 $reset = !$delete; 994 } else { 995 if (!empty($options['add'])) { 996 $delete = (bool)(count(array_intersect($options['add'], array( 997 Horde_Imap_Client::FLAG_DELETED 998 )))); 999 } 1000 1001 if (!empty($options['remove'])) { 1002 $reset = !(bool)(count(array_intersect($options['remove'], array( 1003 Horde_Imap_Client::FLAG_DELETED 1004 )))); 1005 } 1006 } 1007 1008 if ($reset) { 1009 $this->_sendLine('RSET'); 1010 } elseif ($delete) { 1011 foreach ($this->_getSeqIds($options['ids']) as $id) { 1012 try { 1013 $this->_sendLine('DELE ' . $id); 1014 $this->_deleted[] = $id; 1015 } catch (Horde_Imap_Client_Exception $e) {} 1016 } 1017 } 1018 1019 return $this->getIdsOb(); 1020 } 1021 1022 /** 1023 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1024 */ 1025 protected function _copy(Horde_Imap_Client_Mailbox $dest, $options) 1026 { 1027 throw new Horde_Imap_Client_Exception_NoSupportPop3('Copying messages'); 1028 } 1029 1030 /** 1031 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1032 */ 1033 protected function _setQuota(Horde_Imap_Client_Mailbox $root, $options) 1034 { 1035 throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas'); 1036 } 1037 1038 /** 1039 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1040 */ 1041 protected function _getQuota(Horde_Imap_Client_Mailbox $root) 1042 { 1043 throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas'); 1044 } 1045 1046 /** 1047 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1048 */ 1049 protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox) 1050 { 1051 throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas'); 1052 } 1053 1054 /** 1055 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1056 */ 1057 protected function _setACL(Horde_Imap_Client_Mailbox $mailbox, $identifier, 1058 $options) 1059 { 1060 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1061 } 1062 1063 /** 1064 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1065 */ 1066 protected function _deleteACL(Horde_Imap_Client_Mailbox $mailbox, $identifier) 1067 { 1068 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1069 } 1070 1071 /** 1072 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1073 */ 1074 protected function _getACL(Horde_Imap_Client_Mailbox $mailbox) 1075 { 1076 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1077 } 1078 1079 /** 1080 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1081 */ 1082 protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox, 1083 $identifier) 1084 { 1085 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1086 } 1087 1088 /** 1089 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1090 */ 1091 protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox) 1092 { 1093 throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs'); 1094 } 1095 1096 /** 1097 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1098 */ 1099 protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox, 1100 $entries, $options) 1101 { 1102 throw new Horde_Imap_Client_Exception_NoSupportPop3('Metadata'); 1103 } 1104 1105 /** 1106 * @throws Horde_Imap_Client_Exception_NoSupportPop3 1107 */ 1108 protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox, $data) 1109 { 1110 throw new Horde_Imap_Client_Exception_NoSupportPop3('Metadata'); 1111 } 1112 1113 /** 1114 */ 1115 protected function _getSearchCache($type, $options) 1116 { 1117 /* POP3 does not support search caching. */ 1118 return null; 1119 } 1120 1121 /** 1122 */ 1123 public function resolveIds(Horde_Imap_Client_Mailbox $mailbox, 1124 Horde_Imap_Client_Ids $ids, $convert = 0) 1125 { 1126 if (!$ids->special && 1127 (!$convert || 1128 (!$ids->sequence && ($convert == 1)) || 1129 $ids->isEmpty())) { 1130 return clone $ids; 1131 } 1132 1133 $uids = $this->_pop3Cache('uidl'); 1134 1135 return $this->getIdsOb( 1136 $ids->all ? array_values($uids) : array_intersect_keys($uids, $ids->ids) 1137 ); 1138 } 1139 1140 /* Internal functions. */ 1141 1142 /** 1143 * Perform a command on the server. A connection to the server must have 1144 * already been made. 1145 * 1146 * @param string $cmd The command to execute. 1147 * @param array $options Additional options: 1148 * <pre> 1149 * - debug: (string) When debugging, send this string instead of the 1150 * actual command/data sent. 1151 * DEFAULT: Raw data output to debug stream. 1152 * - multiline: (mixed) 'array', 'none', or 'stream'. 1153 * </pre> 1154 * 1155 * @return array See _getResponse(). 1156 * 1157 * @throws Horde_Imap_Client_Exception 1158 */ 1159 protected function _sendLine($cmd, $options = array()) 1160 { 1161 $old_debug = $this->_debug->debug; 1162 if (!empty($options['debug'])) { 1163 $this->_debug->raw($options['debug'] . "\n"); 1164 $this->_debug->debug = false; 1165 } 1166 1167 if ($old_debug) { 1168 $timer = new Horde_Support_Timer(); 1169 $timer->push(); 1170 } 1171 1172 try { 1173 $this->_connection->write($cmd); 1174 } catch (Horde_Imap_Client_Exception $e) { 1175 $this->_debug->debug = $old_debug; 1176 throw $e; 1177 } 1178 1179 $this->_debug->debug = $old_debug; 1180 1181 $resp = $this->_getResponse( 1182 empty($options['multiline']) ? false : $options['multiline'] 1183 ); 1184 1185 if ($old_debug) { 1186 $this->_debug->info(sprintf( 1187 'Command took %s seconds.', 1188 round($timer->pop(), 4) 1189 )); 1190 } 1191 1192 return $resp; 1193 } 1194 1195 /** 1196 * Gets a line from the stream and parses it. 1197 * 1198 * @param mixed $multiline 'array', 'none', 'stream', or null. 1199 * 1200 * @return array An array with the following keys: 1201 * - data: (mixed) Stream, array, or null. 1202 * - resp: (string) The server response text. 1203 * 1204 * @throws Horde_Imap_Client_Exception 1205 */ 1206 protected function _getResponse($multiline = false) 1207 { 1208 $ob = array('resp' => ''); 1209 1210 $read = explode(' ', rtrim($this->_connection->read(), "\r\n"), 2); 1211 if (!in_array($read[0], array('+OK', '-ERR', '+'))) { 1212 $this->_debug->info('ERROR: IMAP read/timeout error.'); 1213 throw new Horde_Imap_Client_Exception( 1214 Horde_Imap_Client_Translation::r("Error when communicating with the mail server."), 1215 Horde_Imap_Client_Exception::SERVER_READERROR 1216 ); 1217 } 1218 1219 $respcode = null; 1220 if (isset($read[1]) && 1221 isset($this->_init['capability']) && 1222 $this->queryCapability('RESP-CODES')) { 1223 $respcode = $this->_parseResponseCode($read[1]); 1224 } 1225 1226 switch ($read[0]) { 1227 case '+OK': 1228 case '+': 1229 if ($respcode) { 1230 $ob['resp'] = $respcode->text; 1231 } elseif (isset($read[1])) { 1232 $ob['resp'] = $read[1]; 1233 } 1234 break; 1235 1236 case '-ERR': 1237 $errcode = 0; 1238 if ($respcode) { 1239 $errtext = $respcode->text; 1240 1241 if (isset($respcode->code)) { 1242 switch ($respcode->code) { 1243 // RFC 2449 [8.1.1] 1244 case 'IN-USE': 1245 // RFC 2449 [8.1.2] 1246 case 'LOGIN-DELAY': 1247 $errcode = Horde_Imap_Client_Exception::LOGIN_UNAVAILABLE; 1248 break; 1249 1250 // RFC 3206 [4] 1251 case 'SYS/TEMP': 1252 $errcode = Horde_Imap_Client_Exception::POP3_TEMP_ERROR; 1253 break; 1254 1255 // RFC 3206 [4] 1256 case 'SYS/PERM': 1257 $errcode = Horde_Imap_Client_Exception::POP3_PERM_ERROR; 1258 break; 1259 1260 // RFC 3206 [5] 1261 case 'AUTH': 1262 $errcode = Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED; 1263 break; 1264 } 1265 } 1266 } elseif (isset($read[1])) { 1267 $errtext = $read[1]; 1268 } else { 1269 $errtext = '[No error message provided by server]'; 1270 } 1271 1272 $e = new Horde_Imap_Client_Exception( 1273 Horde_Imap_Client_Translation::r("POP3 error reported by server."), 1274 $errcode 1275 ); 1276 $e->details = $errtext; 1277 throw $e; 1278 } 1279 1280 switch ($multiline) { 1281 case 'array': 1282 $ob['data'] = array(); 1283 break; 1284 1285 case 'none': 1286 $ob['data'] = null; 1287 break; 1288 1289 case 'stream': 1290 $ob['data'] = fopen('php://temp', 'r+'); 1291 break; 1292 1293 default: 1294 return $ob; 1295 } 1296 1297 do { 1298 $orig_read = $this->_connection->read(); 1299 $read = rtrim($orig_read, "\r\n"); 1300 1301 if ($read === '.') { 1302 break; 1303 } elseif (substr($read, 0, 2) === '..') { 1304 $read = substr($read, 1); 1305 } 1306 1307 if (is_array($ob['data'])) { 1308 $ob['data'][] = $read; 1309 } elseif (!is_null($ob['data'])) { 1310 fwrite($ob['data'], $orig_read); 1311 } 1312 } while (true); 1313 1314 return $ob; 1315 } 1316 1317 /** 1318 * Returns a list of sequence IDs. 1319 * 1320 * @param Horde_Imap_Client_Ids $ids The ID list. 1321 * 1322 * @return array A list of sequence IDs. 1323 */ 1324 protected function _getSeqIds(Horde_Imap_Client_Ids $ids) 1325 { 1326 if (!count($ids)) { 1327 $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES); 1328 return range(1, $status['messages']); 1329 } elseif ($ids->sequence) { 1330 return $ids->ids; 1331 } 1332 1333 return array_keys(array_intersect($this->_pop3Cache('uidl'), $ids->ids)); 1334 } 1335 1336 /** 1337 * Parses response text for response codes (RFC 2449 [8]). 1338 * 1339 * @param string $text The response text. 1340 * 1341 * @return object An object with the following properties: 1342 * - code: (string) The response code, if it exists. 1343 * - data: (string) The response code data, if it exists. 1344 * - text: (string) The human-readable response text. 1345 */ 1346 protected function _parseResponseCode($text) 1347 { 1348 $ret = new stdClass; 1349 1350 $text = trim($text); 1351 if ($text[0] === '[') { 1352 $pos = strpos($text, ' ', 2); 1353 $end_pos = strpos($text, ']', 2); 1354 if ($pos > $end_pos) { 1355 $ret->code = strtoupper(substr($text, 1, $end_pos - 1)); 1356 } else { 1357 $ret->code = strtoupper(substr($text, 1, $pos - 1)); 1358 $ret->data = substr($text, $pos + 1, $end_pos - $pos - 1); 1359 } 1360 $ret->text = trim(substr($text, $end_pos + 1)); 1361 } else { 1362 $ret->text = $text; 1363 } 1364 1365 return $ret; 1366 } 1367 1368 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |