Overview

Packages

  • Imap
    • Client

Classes

  • Horde_Imap_Client
  • Horde_Imap_Client_Auth_DigestMD5
  • Horde_Imap_Client_Base
  • Horde_Imap_Client_Cache
  • Horde_Imap_Client_Data_Acl
  • Horde_Imap_Client_Data_AclCommon
  • Horde_Imap_Client_Data_AclNegative
  • Horde_Imap_Client_Data_AclRights
  • Horde_Imap_Client_Data_Envelope
  • Horde_Imap_Client_Data_Fetch
  • Horde_Imap_Client_Data_Fetch_Pop3
  • Horde_Imap_Client_Data_Thread
  • Horde_Imap_Client_DateTime
  • Horde_Imap_Client_Exception
  • Horde_Imap_Client_Exception_NoSupportExtension
  • Horde_Imap_Client_Fetch_Query
  • Horde_Imap_Client_Ids
  • Horde_Imap_Client_Ids_Pop3
  • Horde_Imap_Client_Mailbox
  • Horde_Imap_Client_Search_Query
  • Horde_Imap_Client_Socket
  • Horde_Imap_Client_Socket_Pop3
  • Horde_Imap_Client_Sort
  • Horde_Imap_Client_Translation
  • Horde_Imap_Client_Utf7imap
  • Horde_Imap_Client_Utils
  • Horde_Imap_Client_Utils_Pop3
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * An interface to a POP3 server using PHP functions.
   4:  *
   5:  * It is an abstraction layer allowing POP3 commands to be used based on
   6:  * IMAP equivalents.
   7:  *
   8:  * This driver implements the following POP3-related RFCs:
   9:  *   - STD 53/RFC 1939: POP3 specification
  10:  *   - RFC 2195: CRAM-MD5 authentication
  11:  *   - RFC 2449: POP3 extension mechanism
  12:  *   - RFC 2595/4616: PLAIN authentication
  13:  *   - RFC 2831: DIGEST-MD5 SASL Authentication (obsoleted by RFC 6331)
  14:  *   - RFC 3206: AUTH/SYS response codes
  15:  *   - RFC 1734/5034: POP3 SASL
  16:  *
  17:  * ---------------------------------------------------------------------------
  18:  *
  19:  * Originally based on the PEAR Net_POP3 package (version 1.3.6) by:
  20:  *     Richard Heyes <richard@phpguru.org>
  21:  *     Damian Fernandez Sosa <damlists@cnba.uba.ar>
  22:  *
  23:  * Copyright (c) 2002, Richard Heyes
  24:  * All rights reserved.
  25:  *
  26:  * Redistribution and use in source and binary forms, with or without
  27:  * modification, are permitted provided that the following conditions
  28:  * are met:
  29:  *
  30:  * o Redistributions of source code must retain the above copyright
  31:  *   notice, this list of conditions and the following disclaimer.
  32:  * o Redistributions in binary form must reproduce the above copyright
  33:  *   notice, this list of conditions and the following disclaimer in the
  34:  *   documentation and/or other materials provided with the distribution.
  35:  * o The names of the authors may not be used to endorse or promote
  36:  *   products derived from this software without specific prior written
  37:  *   permission.
  38:  *
  39:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  40:  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  41:  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  42:  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  43:  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  44:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  45:  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  46:  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  47:  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  48:  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  49:  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  50:  *
  51:  * ---------------------------------------------------------------------------
  52:  *
  53:  * Copyright 2009-2012 Horde LLC (http://www.horde.org/)
  54:  *
  55:  * See the enclosed file COPYING for license information (LGPL). If you
  56:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  57:  *
  58:  * @author   Michael Slusarz <slusarz@horde.org>
  59:  * @category Horde
  60:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
  61:  * @package  Imap_Client
  62:  */
  63: class Horde_Imap_Client_Socket_Pop3 extends Horde_Imap_Client_Base
  64: {
  65:     /**
  66:      * The list of deleted messages.
  67:      *
  68:      * @var array
  69:      */
  70:     protected $_deleted = array();
  71: 
  72:     /**
  73:      * This object returns POP3 Fetch data objects.
  74:      *
  75:      * @var string
  76:      */
  77:     protected $_fetchDataClass = 'Horde_Imap_Client_Data_Fetch_Pop3';
  78: 
  79:     /**
  80:      * The socket connection to the POP3 server.
  81:      *
  82:      * @var resource
  83:      */
  84:     protected $_stream = null;
  85: 
  86:     /**
  87:      */
  88:     protected $_utilsClass = 'Horde_Imap_Client_Utils_Pop3';
  89: 
  90:     /**
  91:      */
  92:     public function __construct(array $params = array())
  93:     {
  94:         if (empty($params['port'])) {
  95:             $params['port'] = (isset($params['secure']) && ($params['secure'] == 'ssl'))
  96:                 ? 995
  97:                 : 110;
  98:         }
  99: 
 100:         parent::__construct($params);
 101:     }
 102: 
 103:     /**
 104:      */
 105:     protected function _initCache($current = false)
 106:     {
 107:         return parent::_initCache($current) &&
 108:                $this->queryCapability('UIDL');
 109:     }
 110: 
 111:     /**
 112:      */
 113:     public function getIdsOb($ids = null, $sequence = false)
 114:     {
 115:         return new Horde_Imap_Client_Ids_Pop3($ids, $sequence);
 116:     }
 117: 
 118:     /**
 119:      */
 120:     protected function _capability()
 121:     {
 122:         $this->_connect();
 123: 
 124:         $capability = array();
 125: 
 126:         try {
 127:             $this->_sendLine('CAPA');
 128: 
 129:             foreach ($this->_getMultiline(true) as $val) {
 130:                 $prefix = explode(' ', $val);
 131: 
 132:                 $capability[strtoupper($prefix[0])] = (count($prefix) > 1)
 133:                     ? array_slice($prefix, 1)
 134:                     : true;
 135:             }
 136:         } catch (Horde_Imap_Client_Exception $e) {
 137:             /* Need to probe for capabilities if CAPA command is not
 138:              * available. */
 139:             $capability = array('USER', 'SASL');
 140: 
 141:             try {
 142:                 $this->_sendLine('UIDL');
 143:                 fclose($this->_getMultiline());
 144:                 $capability[] = 'UIDL';
 145:             } catch (Horde_Imap_Client_Exception $e) {}
 146: 
 147:             try {
 148:                 $this->_sendLine('TOP 1 0');
 149:                 fclose($this->_getMultiline());
 150:                 $capability[] = 'TOP';
 151:             } catch (Horde_Imap_Client_Exception $e) {}
 152:         }
 153: 
 154:         $this->_setInit('capability', $capability);
 155: 
 156:         return $this->_init['capability'];
 157:     }
 158: 
 159:     /**
 160:      */
 161:     protected function _noop()
 162:     {
 163:         $this->_sendLine('NOOP');
 164:     }
 165: 
 166:     /**
 167:      */
 168:     protected function _getNamespaces()
 169:     {
 170:         $this->_exception('IMAP namespaces not supported on POP3 servers.', 'NO_SUPPORT');
 171:     }
 172: 
 173:     /**
 174:      */
 175:     public function alerts()
 176:     {
 177:         return array();
 178:     }
 179: 
 180:     /**
 181:      */
 182:     protected function _login()
 183:     {
 184:         $this->_connect();
 185: 
 186:         // Switch to secure channel if using TLS.
 187:         if (!$this->_isSecure &&
 188:             ($this->_params['secure'] == 'tls')) {
 189:             // Switch over to a TLS connection.
 190:             if (!$this->queryCapability('STLS')) {
 191:                 $this->_exception(Horde_Imap_Client_Translation::t("Could not open secure connection to the POP3 server.") . ' ' . Horde_Imap_Client_Translation::t("Server does not support secure connections."), 'LOGIN_TLSFAILURE');
 192:             }
 193: 
 194:             $this->_sendLine('STLS');
 195: 
 196:             $res = @stream_socket_enable_crypto($this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
 197: 
 198:             if (!$res) {
 199:                 $this->logout();
 200:                 $this->_exception(Horde_Imap_Client_Translation::t("Could not open secure connection to the POP3 server."), 'LOGIN_TLSFAILURE');
 201:             }
 202: 
 203:             // Expire cached CAPABILITY information
 204:             $this->_setInit('capability');
 205: 
 206:             $this->_isSecure = true;
 207:         }
 208: 
 209:         if (empty($this->_init['authmethod'])) {
 210:             $auth_mech = ($sasl = $this->queryCapability('SASL'))
 211:                 ? $sasl
 212:                 : array();
 213: 
 214:             if (isset($this->_temp['pop3timestamp'])) {
 215:                 $auth_mech[] = 'APOP';
 216:             }
 217: 
 218:             $auth_mech[] = 'USER';
 219:         } else {
 220:             $auth_mech = array($this->_init['authmethod']);
 221:         }
 222: 
 223:         foreach ($auth_mech as $method) {
 224:             try {
 225:                 $this->_tryLogin($method);
 226:                 $this->_setInit('authmethod', $method);
 227:                 return true;
 228:             } catch (Horde_Imap_Client_Exception $e) {
 229:                 if (!empty($this->_init['authmethod'])) {
 230:                     $this->_setInit();
 231:                     return $this->login();
 232:                 }
 233:             }
 234:         }
 235: 
 236:         $this->_exception(Horde_Imap_Client_Translation::t("POP3 server denied authentication."), $e->getCode() ? $e->getCode() : 'LOGIN_AUTHENTICATIONFAILED');
 237:     }
 238: 
 239:     /**
 240:      * Connects to the server.
 241:      *
 242:      * @throws Horde_Imap_Client_Exception
 243:      */
 244:     protected function _connect()
 245:     {
 246:         if (!is_null($this->_stream)) {
 247:             return;
 248:         }
 249: 
 250:         if (!empty($this->_params['secure']) && !extension_loaded('openssl')) {
 251:             new InvalidArgumentException('Secure connections require the PHP openssl extension.');
 252:         }
 253: 
 254:         switch ($this->_params['secure']) {
 255:         case 'ssl':
 256:         case 'sslv2':
 257:         case 'sslv3':
 258:             $conn = $this->_params['secure'] . '://';
 259:             $this->_isSecure = true;
 260:             break;
 261: 
 262:         case 'tls':
 263:         default:
 264:             $conn = 'tcp://';
 265:             break;
 266:         }
 267: 
 268:         $this->_stream = @stream_socket_client($conn . $this->_params['hostspec'] . ':' . $this->_params['port'], $error_number, $error_string, $this->_params['timeout']);
 269: 
 270:         if ($this->_stream === false) {
 271:             $this->_stream = null;
 272:             $this->_isSecure = false;
 273:             $this->_exception(array(
 274:                 Horde_Imap_Client_Translation::t("Error connecting to POP3 server."),
 275:                 sprintf("[%u] %s.", $error_number, $error_string)
 276:             ), 'SERVER_CONNECT');
 277:         }
 278: 
 279:         stream_set_timeout($this->_stream, $this->_params['timeout']);
 280: 
 281:         $line = $this->_getLine();
 282: 
 283:         // Check for string matching APOP timestamp
 284:         if (preg_match('/<.+@.+>/U', $line['line'], $matches)) {
 285:             $this->_temp['pop3timestamp'] = $matches[0];
 286:         }
 287:     }
 288: 
 289:     /**
 290:      * Authenticate to the POP3 server.
 291:      *
 292:      * @param string $method  POP3 login method.
 293:      *
 294:      * @throws Horde_Imap_Client_Exception
 295:      */
 296:     protected function _tryLogin($method)
 297:     {
 298:         switch ($method) {
 299:         case 'CRAM-MD5':
 300:         case 'CRAM-SHA1':
 301:         case 'CRAM-SHA256':
 302:             // RFC 5034: CRAM-MD5
 303:             // CRAM-SHA1 & CRAM-SHA256 supported by Courier SASL library
 304:             $challenge = $this->_sendLine('AUTH ' . $method);
 305:             $response = base64_encode($this->_params['username'] . ' ' . hash_hmac(strtolower(substr($method, 5)), base64_decode(substr($challenge['line'], 2)), $this->getParam('password'), true));
 306:             $this->_sendLine($response, array(
 307:                 'debug' => '[' . $method . ' Response]'
 308:             ));
 309:             break;
 310: 
 311:         case 'DIGEST-MD5':
 312:             // RFC 2831; Obsoleted by RFC 6331
 313:             $challenge = $this->_sendLine('AUTH DIGEST-MD5');
 314:             $response = base64_encode(new Horde_Imap_Client_Auth_DigestMD5(
 315:                 $this->_params['username'],
 316:                 $this->getParam('password'),
 317:                 base64_decode(substr($challenge['line'], 2)),
 318:                 $this->_params['hostspec'],
 319:                 'pop3'
 320:             ));
 321:             $sresponse = $this->_sendLine($response, array(
 322:                 'debug' => '[DIGEST-MD5 Response]'
 323:             ));
 324:             if (stripos(base64_decode(substr($sresponse['line'], 2)), 'rspauth=') === false) {
 325:                 $this->_exception(Horde_Imap_Client_Translation::t("Unexpected response from server when authenticating."), 'SERVER_CONNECT');
 326:             }
 327: 
 328:             /* POP3 doesn't use protocol's third step. */
 329:             $this->_sendLine('');
 330:             break;
 331: 
 332:         case 'LOGIN':
 333:             // RFC 5034
 334:             $this->_sendLine('AUTH LOGIN');
 335:             $this->_sendLine(base64_encode($this->_params['username']));
 336:             $this->_sendLine(base64_encode($this->getParam('password')), array(
 337:                 'debug' => '[AUTH LOGIN Command - password]'
 338:             ));
 339:             break;
 340: 
 341:         case 'PLAIN':
 342:             // RFC 5034
 343:             $this->_sendLine('AUTH PLAIN ' . base64_encode(chr(0) . $this->_params['username'] . chr(0) . $this->getParam('password')), array(
 344:                 'debug' => sprintf('[AUTH PLAIN Command - username: %s]', $this->_params['username'])
 345:             ));
 346:             break;
 347: 
 348:         case 'APOP':
 349:             // RFC 1939 [7]
 350:             $this->_sendLine('APOP ' . $this->_params['username'] . ' ' . hash('md5', $this->_temp['pop3timestamp'] . $this->_params['password']));
 351:             break;
 352: 
 353:         case 'USER':
 354:             // RFC 1939 [7]
 355:             $this->_sendLine('USER ' . $this->_params['username']);
 356:             $this->_sendLine('PASS ' . $this->getParam('password'), array(
 357:                 'debug' => '[USER Command - password]'
 358:             ));
 359:             break;
 360: 
 361:         default:
 362:             $this->_exception(sprintf(Horde_Imap_Client_Translation::t("Unknown authentication method: %s"), $method), 'SERVER_CONNECT');
 363:         }
 364:     }
 365: 
 366:     /**
 367:      */
 368:     protected function _logout()
 369:     {
 370:         if (!is_null($this->_stream)) {
 371:             try {
 372:                 $this->_sendLine('QUIT');
 373:             } catch (Horde_Imap_Client_Exception $e) {}
 374:             fclose($this->_stream);
 375:             $this->_stream = null;
 376:             $this->_deleted = array();
 377:         }
 378:     }
 379: 
 380:     /**
 381:      */
 382:     protected function _sendID($info)
 383:     {
 384:         $this->_exception('IMAP ID command not supported on POP3 servers.', 'NO_SUPPORT');
 385:     }
 386: 
 387:     /**
 388:      * Return implementation information from the POP3 server (RFC 2449 [6.9]).
 389:      */
 390:     protected function _getID()
 391:     {
 392:         $id = $this->queryCapability('IMPLEMENTATION');
 393:         return empty($id)
 394:             ? array()
 395:             : array('implementation' => $id);
 396:     }
 397: 
 398:     /**
 399:      */
 400:     protected function _setLanguage($langs)
 401:     {
 402:         $this->_exception('IMAP LANGUAGE extension not supported on POP3 servers.', 'NO_SUPPORT');
 403:     }
 404: 
 405:     /**
 406:      */
 407:     protected function _getLanguage($list)
 408:     {
 409:         $this->_exception('IMAP LANGUAGE extension not supported on POP3 servers.', 'NO_SUPPORT');
 410:     }
 411: 
 412:     /**
 413:      */
 414:     protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox, $mode)
 415:     {
 416:         if (strcasecmp($mailbox, 'INBOX') !== 0) {
 417:             $this->_exception('Mailboxes other than INBOX not supported on POP3 servers.', 'NO_SUPPORT');
 418:         }
 419:     }
 420: 
 421:     /**
 422:      */
 423:     protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox, $opts)
 424:     {
 425:         $this->_exception('Creating mailboxes not supported on POP3 servers.', 'NO_SUPPORT');
 426:     }
 427: 
 428:     /**
 429:      */
 430:     protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox)
 431:     {
 432:         $this->_exception('Deleting mailboxes not supported on POP3 servers.', 'NO_SUPPORT');
 433:     }
 434: 
 435:     /**
 436:      */
 437:     protected function _renameMailbox(Horde_Imap_Client_Mailbox $old,
 438:                                       Horde_Imap_Client_Mailbox $new)
 439:     {
 440:         $this->_exception('Renaming mailboxes not supported on POP3 servers.', 'NO_SUPPORT');
 441:     }
 442: 
 443:     /**
 444:      */
 445:     protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox,
 446:                                          $subscribe)
 447:     {
 448:         $this->_exception('Mailboxes other than INBOX not supported on POP3 servers.', 'NO_SUPPORT');
 449:     }
 450: 
 451:     /**
 452:      */
 453:     protected function _listMailboxes($pattern, $mode, $options)
 454:     {
 455:         $tmp = array(
 456:             'mailbox' => Horde_Imap_Client_Mailbox::get('INBOX')
 457:         );
 458: 
 459:         if (!empty($options['attributes'])) {
 460:             $tmp['attributes'] = array();
 461:         }
 462:         if (!empty($options['delimiter'])) {
 463:             $tmp['delimiter'] = '';
 464:         }
 465: 
 466:         return array('INBOX' => $tmp);
 467:     }
 468: 
 469:     /**
 470:      * @param integer $flags   This driver only supports the options listed
 471:      *                         under Horde_Imap_Client::STATUS_ALL.
 472:      */
 473:     protected function _status(Horde_Imap_Client_Mailbox $mailbox, $flags)
 474:     {
 475:         $this->openMailbox($mailbox);
 476: 
 477:         // This driver only supports the base flags given by c-client.
 478:         if (($flags & Horde_Imap_Client::STATUS_FIRSTUNSEEN) ||
 479:             ($flags & Horde_Imap_Client::STATUS_FLAGS) ||
 480:             ($flags & Horde_Imap_Client::STATUS_PERMFLAGS) ||
 481:             ($flags & Horde_Imap_Client::STATUS_HIGHESTMODSEQ) ||
 482:             ($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY)) {
 483:             $this->_exception('Improper status request on POP3 server.', 'NO_SUPPORT');
 484:         }
 485: 
 486:         $ret = array();
 487: 
 488:         if ($flags & Horde_Imap_Client::STATUS_MESSAGES) {
 489:             $res = $this->_pop3Cache('stat');
 490:             $ret['messages'] = $res['msgs'];
 491:         }
 492: 
 493:         if ($flags & Horde_Imap_Client::STATUS_RECENT) {
 494:             $res = $this->_pop3Cache('stat');
 495:             $ret['recent'] = $res['msgs'];
 496:         }
 497: 
 498:         if ($flags & Horde_Imap_Client::STATUS_UIDNEXT) {
 499:             $res = $this->_pop3Cache('stat');
 500:             $ret['uidnext'] = $res['msgs'] + 1;
 501:         }
 502: 
 503:         if ($flags & Horde_Imap_Client::STATUS_UIDVALIDITY) {
 504:             $ret['uidvalidity'] = $this->queryCapability('UIDL')
 505:                 ? 1
 506:                 : microtime(true);
 507:         }
 508: 
 509:         if ($flags & Horde_Imap_Client::STATUS_UNSEEN) {
 510:             $ret['unseen'] = 0;
 511:         }
 512: 
 513:         return $ret;
 514:     }
 515: 
 516:     /**
 517:      */
 518:     protected function _append(Horde_Imap_Client_Mailbox $mailbox, $data,
 519:                                $options)
 520:     {
 521:         $this->_exception('Appending messages not supported on POP3 servers.', 'NO_SUPPORT');
 522:     }
 523: 
 524:     /**
 525:      */
 526:     protected function _check()
 527:     {
 528:         $this->noop();
 529:     }
 530: 
 531:     /**
 532:      */
 533:     protected function _close($options)
 534:     {
 535:         if (!empty($options['expunge'])) {
 536:             $this->logout();
 537:         }
 538:     }
 539: 
 540:     /**
 541:      * @param array $options  Additional options. 'ids' has no effect in this
 542:      *                        driver.
 543:      */
 544:     protected function _expunge($options)
 545:     {
 546:         $msg_list = $this->_deleted;
 547:         $this->logout();
 548:         return empty($options['list'])
 549:             ? null
 550:             : $msg_list;
 551:     }
 552: 
 553:     /**
 554:      */
 555:     protected function _search($query, $options)
 556:     {
 557:         $sort = empty($options['sort'])
 558:             ? null
 559:             : reset($options['sort']);
 560: 
 561:         // Only support a single query: an ALL search sorted by sequence.
 562:         if ((reset($options['_query']['query']) != 'ALL') ||
 563:             ($sort &&
 564:              ((count($options['sort']) > 1) ||
 565:               ($sort != Horde_Imap_Client::SORT_SEQUENCE)))) {
 566:             $this->_exception('Server search not supported on POP3 server.', 'NO_SUPPORT');
 567:         }
 568: 
 569:         $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
 570:         $res = range(1, $status['messages']);
 571: 
 572:         if (empty($options['sequence'])) {
 573:             $tmp = array();
 574:             $uidllist = $this->_pop3Cache('uidl');
 575:             foreach ($res as $val) {
 576:                 $tmp[] = $uidllist[$val];
 577:             }
 578:             $res = $tmp;
 579:         }
 580: 
 581:         $ret = array();
 582:         foreach ($options['results'] as $val) {
 583:             switch ($val) {
 584:             case Horde_Imap_Client::SEARCH_RESULTS_COUNT:
 585:                 $ret['count'] = count($res);
 586:                 break;
 587: 
 588:             case Horde_Imap_Client::SEARCH_RESULTS_MATCH:
 589:                 $ret['match'] = $this->getIdsOb($res);
 590:                 break;
 591: 
 592:             case Horde_Imap_Client::SEARCH_RESULTS_MAX:
 593:                 $ret['max'] = empty($res) ? null : max($res);
 594:                 break;
 595: 
 596:             case Horde_Imap_Client::SEARCH_RESULTS_MIN:
 597:                 $ret['min'] = empty($res) ? null : min($res);
 598:                 break;
 599:             }
 600:         }
 601: 
 602:         return $ret;
 603:     }
 604: 
 605:     /**
 606:      */
 607:     protected function _setComparator($comparator)
 608:     {
 609:         $this->_exception('Search comparators not supported on POP3 server.', 'NO_SUPPORT');
 610:     }
 611: 
 612:     /**
 613:      */
 614:     protected function _getComparator()
 615:     {
 616:         $this->_exception('Search comparators not supported on POP3 server.', 'NO_SUPPORT');
 617:     }
 618: 
 619:     /**
 620:      */
 621:     protected function _thread($options)
 622:     {
 623:         $this->_exception('Server threading not supported on POP3 server.', 'NO_SUPPORT');
 624:     }
 625: 
 626:     /**
 627:      */
 628:     protected function _fetch($query, $results, $options)
 629:     {
 630:         // These options are not supported by this driver.
 631:         if (!empty($options['changedsince']) ||
 632:             !empty($options['vanished'])) {
 633:             $this->_exception('Fetch options not supported on POP3 server.', 'NO_SUPPORT');
 634:         }
 635: 
 636:         // Grab sequence IDs - IDs will always be the message number for
 637:         // POP3 fetch commands.
 638:         $seq_ids = $this->_getSeqIds($options['ids']);
 639:         if (empty($seq_ids)) {
 640:             return $results;
 641:         }
 642: 
 643:         $lookup = $options['ids']->sequence
 644:             ? array_combine($seq_ids, $seq_ids)
 645:             : $this->_pop3Cache('uidl');
 646: 
 647:         foreach ($query as $type => $c_val) {
 648:             switch ($type) {
 649:             case Horde_Imap_Client::FETCH_FULLMSG:
 650:                 foreach ($seq_ids as $id) {
 651:                     $tmp = $this->_pop3Cache('msg', $id);
 652: 
 653:                     if (empty($c_val['start']) && empty($c_val['length'])) {
 654:                         $tmp2 = fopen('php://temp', 'r+');
 655:                         stream_copy_to_stream($tmp, $tmp2, empty($c_val['length']) ? -1 : $c_val['length'], empty($c_val['start']) ? 0 : $c_val['start']);
 656:                         $results[$lookup[$id]]->setFullMsg($tmp2);
 657:                     } else {
 658:                         $results[$lookup[$id]]->setFullMsg($tmp);
 659:                     }
 660:                 }
 661:                 break;
 662: 
 663:             case Horde_Imap_Client::FETCH_HEADERTEXT:
 664:                 // Ignore 'peek' option
 665:                 foreach ($c_val as $key => $val) {
 666:                     foreach ($seq_ids as $id) {
 667:                         /* Message header can be retrieved via TOP, if the
 668:                          * command is available. */
 669:                         try {
 670:                             $tmp = ($key == 0)
 671:                                 ? $this->_pop3Cache('hdr', $id)
 672:                                 : Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key);
 673:                             $results[$lookup[$id]]->setHeaderText($key, $this->_processString($tmp, $c_val));
 674:                         } catch (Horde_Mime_Exception $e) {}
 675:                     }
 676:                 }
 677:                 break;
 678: 
 679:             case Horde_Imap_Client::FETCH_BODYTEXT:
 680:                 // Ignore 'peek' option
 681:                 foreach ($c_val as $key => $val) {
 682:                     foreach ($seq_ids as $id) {
 683:                         try {
 684:                             $results[$lookup[$id]]->setBodyText($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val));
 685:                         } catch (Horde_Mime_Exception $e) {}
 686:                     }
 687:                 }
 688:                 break;
 689: 
 690:             case Horde_Imap_Client::FETCH_MIMEHEADER:
 691:                 // Ignore 'peek' option
 692:                 foreach ($c_val as $key => $val) {
 693:                     foreach ($seq_ids as $id) {
 694:                         try {
 695:                             $results[$lookup[$id]]->setMimeHeader($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key), $val));
 696:                         } catch (Horde_Mime_Exception $e) {}
 697:                     }
 698:                 }
 699:                 break;
 700: 
 701:             case Horde_Imap_Client::FETCH_BODYPART:
 702:                 // Ignore 'decode', 'peek'
 703:                 foreach ($c_val as $key => $val) {
 704:                     foreach ($seq_ids as $id) {
 705:                         try {
 706:                             $results[$lookup[$id]]->setBodyPart($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val));
 707:                         } catch (Horde_Mime_Exception $e) {}
 708:                     }
 709:                 }
 710:                 break;
 711: 
 712:             case Horde_Imap_Client::FETCH_HEADERS:
 713:                 // Ignore 'length', 'peek'
 714:                 foreach ($seq_ids as $id) {
 715:                     $ob = $this->_pop3Cache('hdrob', $id);
 716:                     foreach ($c_val as $key => $val) {
 717:                         $tmp = $ob;
 718: 
 719:                         if (empty($val['notsearch'])) {
 720:                             $tmp2 = $tmp->toArray(array('nowrap' => true));
 721:                             foreach (array_keys($tmp2) as $hdr) {
 722:                                 if (!in_array($hdr, $val['headers'])) {
 723:                                     $tmp->removeHeader($hdr);
 724:                                 }
 725:                             }
 726:                         } else {
 727:                             foreach ($val['headers'] as $hdr) {
 728:                                 $tmp->removeHeader($hdr);
 729:                             }
 730:                         }
 731: 
 732:                         $results[$lookup[$id]]->setHeaders($key, $tmp);
 733:                     }
 734:                 }
 735:                 break;
 736: 
 737:             case Horde_Imap_Client::FETCH_STRUCTURE:
 738:                 foreach ($seq_ids as $id) {
 739:                     if ($ptr = $this->_pop3Cache('msg', $id)) {
 740:                         try {
 741:                             $results[$lookup[$id]]->setStructure(Horde_Mime_Part::parseMessage(stream_get_contents($ptr)));
 742:                         } catch (Horde_Exception $e) {}
 743:                     }
 744:                 }
 745:                 break;
 746: 
 747:             case Horde_Imap_Client::FETCH_ENVELOPE:
 748:                 $rfc822 = new Horde_Mail_Rfc822();
 749:                 foreach ($seq_ids as $id) {
 750:                     $tmp = $this->_pop3Cache('hdrob', $id);
 751:                     $results[$lookup[$id]]->setEnvelope(array(
 752:                         'date' => $tmp->getValue('date'),
 753:                         'subject' => $tmp->getValue('subject'),
 754:                         'from' => $tmp->getOb('from'),
 755:                         'sender' => $tmp->getOb('sender'),
 756:                         'reply_to' => $tmp->getOb('reply-to'),
 757:                         'to' => $rfc822->parseAddressList(Horde_Mime_Address::addrArray2String($tmp->getOb('to'))),
 758:                         'cc' => $rfc822->parseAddressList(Horde_Mime_Address::addrArray2String($tmp->getOb('cc'))),
 759:                         'bcc' => $rfc822->parseAddressList(Horde_Mime_Address::addrArray2String($tmp->getOb('bcc'))),
 760:                         'in_reply_to' => $tmp->getValue('in-reply-to'),
 761:                         'message_id' => $tmp->getValue('message-id')
 762:                     ));
 763:                 }
 764:                 break;
 765: 
 766:             case Horde_Imap_Client::FETCH_IMAPDATE:
 767:                 foreach ($seq_ids as $id) {
 768:                     $tmp = $this->_pop3Cache('hdrob', $id);
 769:                     $results[$lookup[$id]]->setImapDate($tmp->getValue('date'));
 770:                 }
 771:                 break;
 772: 
 773:             case Horde_Imap_Client::FETCH_SIZE:
 774:                 $sizelist = $this->_pop3Cache('size');
 775:                 foreach ($seq_ids as $id) {
 776:                     $results[$lookup[$id]]->setSize($sizelist[$id]);
 777:                 }
 778:                 break;
 779: 
 780:             case Horde_Imap_Client::FETCH_SEQ:
 781:                 foreach ($seq_ids as $id) {
 782:                     $results[$lookup[$id]]->setSeq($id);
 783:                 }
 784:                 break;
 785: 
 786:             case Horde_Imap_Client::FETCH_UID:
 787:                 $uidllist = $this->_pop3Cache('uidl');
 788:                 foreach ($seq_ids as $id) {
 789:                     if (isset($uidllist[$id])) {
 790:                         $results[$lookup[$id]]->setUid($uidllist[$id]);
 791:                     }
 792:                 }
 793:                 break;
 794:             }
 795:         }
 796: 
 797:         $this->_updateCache($results, array(
 798:             'seq' => $options['ids']->sequence
 799:         ));
 800: 
 801:         return $results;
 802:     }
 803: 
 804:     /**
 805:      * Retrieve locally cached message data.
 806:      *
 807:      * @param string $type    Either 'hdr', 'hdrob', 'msg', 'size', 'stat',
 808:      *                        or 'uidl'.
 809:      * @param integer $index  The message index.
 810:      * @param mixed $data     Additional information needed.
 811:      *
 812:      * @return mixed  The cached data. 'msg' returns a stream resource. All
 813:      *                other types return strings.
 814:      *
 815:      * @throws Horde_Imap_Client_Exception
 816:      */
 817:     protected function _pop3Cache($type, $index = null, $data = null)
 818:     {
 819:         if (isset($this->_temp['pop3cache'][$index][$type])) {
 820:             if ($type == 'msg') {
 821:                 rewind($this->_temp['pop3cache'][$index][$type]);
 822:             }
 823:             return $this->_temp['pop3cache'][$index][$type];
 824:         }
 825: 
 826:         switch ($type) {
 827:         case 'hdr':
 828:             $data = null;
 829:             if ($this->queryCapability('TOP')) {
 830:                 try {
 831:                     $resp = $this->_sendLine('TOP ' . $index . ' 0');
 832:                     $ptr = $this->_getMultiline();
 833:                     rewind($ptr);
 834:                     $data = stream_get_contents($ptr);
 835:                     fclose($ptr);
 836:                 } catch (Horde_Imap_Client_Exception $e) {}
 837:             }
 838: 
 839:             if (is_null($data)) {
 840:                 $data = Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $index)), 'header', 0);
 841:             }
 842:             break;
 843: 
 844:         case 'hdrob':
 845:             $data = Horde_Mime_Headers::parseHeaders($this->_pop3Cache('hdr', $index));
 846:             break;
 847: 
 848:         case 'msg':
 849:             $resp = $this->_sendLine('RETR ' . $index);
 850:             $data = $this->_getMultiline();
 851:             rewind($data);
 852:             break;
 853: 
 854:         case 'size':
 855:         case 'uidl':
 856:             $data = array();
 857:             try {
 858:                 $this->_sendLine(($type == 'size') ? 'LIST' : 'UIDL');
 859:                 foreach ($this->_getMultiline(true) as $val) {
 860:                     $resp_data = explode(' ', $val, 2);
 861:                     $data[$resp_data[0]] = $resp_data[1];
 862:                 }
 863:             } catch (Horde_Imap_Client_Exception $e) {}
 864:             break;
 865: 
 866:         case 'stat':
 867:             $resp = $this->_sendLine('STAT');
 868:             $resp_data = explode(' ', $resp['line'], 2);
 869:             $data = array('msgs' => $resp_data[0], 'size' => $resp_data[1]);
 870:             break;
 871:         }
 872: 
 873:         $this->_temp['pop3cache'][$index][$type] = $data;
 874: 
 875:         return $data;
 876:     }
 877: 
 878:     /**
 879:      * Process a string response based on criteria options.
 880:      *
 881:      * @param string $str  The original string.
 882:      * @param array $opts  The criteria options.
 883:      *
 884:      * @return string  The requested string.
 885:      */
 886:     protected function _processString($str, $opts)
 887:     {
 888:         if (!empty($opts['length'])) {
 889:             return substr($str, empty($opts['start']) ? 0 : $opts['start'], $opts['length']);
 890:         } elseif (!empty($opts['start'])) {
 891:             return substr($str, $opts['start']);
 892:         }
 893: 
 894:         return $str;
 895:     }
 896: 
 897:     /**
 898:      * @param array $options  Additional options. This driver does not support
 899:      *                        'unchangedsince'.
 900:      */
 901:     protected function _store($options)
 902:     {
 903:         $delete = $reset = false;
 904: 
 905:         /* Only support deleting/undeleting messages. */
 906:         if (isset($options['replace'])) {
 907:             $delete = (bool)(count(array_intersect($options['replace'], array(
 908:                 Horde_Imap_Client::FLAG_DELETED
 909:             ))));
 910:             $reset = !$delete;
 911:         } else {
 912:             if (!empty($options['add'])) {
 913:                 $delete = (bool)(count(array_intersect($options['add'], array(
 914:                     Horde_Imap_Client::FLAG_DELETED
 915:                 ))));
 916:             }
 917: 
 918:             if (!empty($options['remove'])) {
 919:                 $reset = !(bool)(count(array_intersect($options['remove'], array(
 920:                     Horde_Imap_Client::FLAG_DELETED
 921:                 ))));
 922:             }
 923:         }
 924: 
 925:         if ($reset) {
 926:             $this->_sendLine('RSET');
 927:         } elseif ($delete) {
 928:             foreach ($this->_getSeqIds($options['ids']) as $id) {
 929:                 try {
 930:                     $this->_sendLine('DELE ' . $id);
 931:                 } catch (Horde_Imap_Client_Exception $e) {}
 932:             }
 933:         }
 934: 
 935:         return $this->getIdsOb();
 936:     }
 937: 
 938:     /**
 939:      */
 940:     protected function _copy(Horde_Imap_Client_Mailbox $dest, $options)
 941:     {
 942:         $this->_exception('Copying messages not supported on POP3 servers.', 'NO_SUPPORT');
 943:     }
 944: 
 945:     /**
 946:      */
 947:     protected function _setQuota(Horde_Imap_Client_Mailbox $root, $options)
 948:     {
 949:         $this->_exception('IMAP quotas not supported on POP3 servers.', 'NO_SUPPORT');
 950:     }
 951: 
 952:     /**
 953:      */
 954:     protected function _getQuota(Horde_Imap_Client_Mailbox $root)
 955:     {
 956:         $this->_exception('IMAP quotas not supported on POP3 servers.', 'NO_SUPPORT');
 957:     }
 958: 
 959:     /**
 960:      */
 961:     protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox)
 962:     {
 963:         $this->_exception('IMAP quotas not supported on POP3 servers.', 'NO_SUPPORT');
 964:     }
 965: 
 966:     /**
 967:      */
 968:     protected function _setACL(Horde_Imap_Client_Mailbox $mailbox, $identifier,
 969:                                $options)
 970:     {
 971:         $this->_exception('IMAP ACLs not supported on POP3 servers.', 'NO_SUPPORT');
 972:     }
 973: 
 974:     /**
 975:      */
 976:     protected function _getACL(Horde_Imap_Client_Mailbox $mailbox)
 977:     {
 978:         $this->_exception('IMAP ACLs not supported on POP3 servers.', 'NO_SUPPORT');
 979:     }
 980: 
 981:     /**
 982:      */
 983:     protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox,
 984:                                       $identifier)
 985:     {
 986:         $this->_exception('IMAP ACLs not supported on POP3 servers.', 'NO_SUPPORT');
 987:     }
 988: 
 989:     /**
 990:      */
 991:     protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox)
 992:     {
 993:         $this->_exception('IMAP ACLs not supported on POP3 servers.', 'NO_SUPPORT');
 994:     }
 995: 
 996:     /**
 997:      */
 998:     protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox,
 999:                                     $entries, $options)
1000:     {
1001:         $this->_exception('IMAP metadata not supported on POP3 servers.', 'NO_SUPPORT');
1002:     }
1003: 
1004:     /**
1005:      */
1006:     protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox, $data)
1007:     {
1008:         $this->_exception('IMAP metadata not supported on POP3 servers.', 'NO_SUPPORT');
1009:     }
1010: 
1011:     /**
1012:      */
1013:     protected function _getSearchCache($type, $mailbox, $options)
1014:     {
1015:         /* POP3 does not support search caching. */
1016:         return null;
1017:     }
1018: 
1019:     /* Internal functions. */
1020: 
1021:     /**
1022:      * Perform a command on the server. A connection to the server must have
1023:      * already been made.
1024:      *
1025:      * @param string $query   The command to execute.
1026:      * @param array $options  Additional options:
1027:      *   - debug: (string) When debugging, send this string instead of the
1028:      *            actual command/data sent.
1029:      *            DEFAULT: Raw data output to debug stream.
1030:      */
1031:     protected function _sendLine($query, $options = array())
1032:     {
1033:         $this->writeDebug((empty($options['debug']) ? $query : $options['debug']) . "\n", Horde_Imap_Client::DEBUG_CLIENT);
1034: 
1035:         fwrite($this->_stream, $query . "\r\n");
1036: 
1037:         return $this->_getLine();
1038:     }
1039: 
1040:     /**
1041:      * Gets a line from the stream and parses it.
1042:      *
1043:      * @return array  An array with the following keys:
1044:      *   - line: (string) The server response text.
1045:      *   - response: (string) Either 'OK', 'END', '+', or ''.
1046:      *
1047:      * @throws Horde_Imap_Client_Exception
1048:      */
1049:     protected function _getLine()
1050:     {
1051:         $ob = array('line' => '', 'response' => '');
1052: 
1053:         if (feof($this->_stream)) {
1054:             $this->logout();
1055:             $this->_exception(Horde_Imap_Client_Translation::t("POP3 Server closed the connection unexpectedly."), 'DISCONNECT');
1056:         }
1057: 
1058:         $read = rtrim(fgets($this->_stream));
1059:         if (empty($read)) {
1060:             return;
1061:         }
1062: 
1063:         $this->writeDebug($read . "\n", Horde_Imap_Client::DEBUG_SERVER);
1064: 
1065:         $orig_read = $read;
1066:         $read = explode(' ', $read, 2);
1067: 
1068:         switch ($read[0]) {
1069:         case '+OK':
1070:             $ob['response'] = 'OK';
1071:             if (isset($read[1])) {
1072:                 $response = $this->_parseResponseText($read[1]);
1073:                 $ob['line'] = $response->text;
1074:             }
1075:             break;
1076: 
1077:         case '-ERR':
1078:             $errcode = 0;
1079:             if (isset($read[1])) {
1080:                 $response = $this->_parseResponseText($read[1]);
1081:                 $errtext = $response->text;
1082:                 if (isset($response->code)) {
1083:                     switch ($response->code) {
1084:                     // RFC 2449 [8.1.1]
1085:                     case 'IN-USE':
1086:                     // RFC 2449 [8.1.2]
1087:                     case 'LOGIN-DELAY':
1088:                         $errcode = 'LOGIN_UNAVAILABLE';
1089:                         break;
1090: 
1091:                     // RFC 3206 [4]
1092:                     case 'SYS/TEMP':
1093:                         $errcode = 'POP3_TEMP_ERROR';
1094:                         break;
1095: 
1096:                     // RFC 3206 [4]
1097:                     case 'SYS/PERM':
1098:                         $errcode = 'POP3_PERM_ERROR';
1099:                         break;
1100: 
1101:                     // RFC 3206 [5]
1102:                     case 'AUTH':
1103:                         $errcode = 'LOGIN_AUTHENTICATIONFAILED';
1104:                         break;
1105:                     }
1106:                 }
1107:             } else {
1108:                 $errtext = '[No error message provided by server]';
1109:             }
1110: 
1111:             $this->_exception(array(
1112:                 Horde_Imap_Client_Translation::t("POP3 error reported by server."),
1113:                 $errtext
1114:             ), $errcode);
1115: 
1116:         case '.':
1117:             $ob['response'] = 'END';
1118:             break;
1119: 
1120:         case '+':
1121:             $ob['response'] = '+';
1122:             break;
1123: 
1124:         default:
1125:             $ob['line'] = $orig_read;
1126:             break;
1127:         }
1128: 
1129:         return $ob;
1130:     }
1131: 
1132:     /**
1133:      * Obtain multiline input.
1134:      *
1135:      * @param boolean $retarray  Return an array?
1136:      *
1137:      * @return mixed  An array if $retarray is true, a stream resource
1138:      *                otherwise.
1139:      *
1140:      * @throws Horde_Imap_Client_Exception
1141:      */
1142:     protected function _getMultiline($retarray = false)
1143:     {
1144:         $data = $retarray
1145:             ? array()
1146:             : fopen('php://temp', 'r+');
1147: 
1148:         do {
1149:             $line = $this->_getLine();
1150:             if (empty($line['response'])) {
1151:                 if (substr($line['line'], 0, 2) == '..') {
1152:                     $line['line'] = substr($line['line'], 1);
1153:                 }
1154: 
1155:                 if ($retarray) {
1156:                     $data[] = $line['line'];
1157:                 } else {
1158:                     fwrite($data, $line['line'] . "\r\n");
1159:                 }
1160:             }
1161:         } while ($line['response'] != 'END');
1162: 
1163:         return $data;
1164:     }
1165: 
1166:     /**
1167:      * Returns a list of sequence IDs.
1168:      *
1169:      * @param Horde_Imap_Client_Ids $ids  The ID list.
1170:      *
1171:      * @return array  A list of sequence IDs.
1172:      */
1173:     protected function _getSeqIds(Horde_Imap_Client_Ids $ids)
1174:     {
1175:         if (!count($ids)) {
1176:             $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
1177:             return range(1, $status['messages']);
1178:         } elseif ($ids->sequence) {
1179:             return $ids->ids;
1180:         }
1181: 
1182:         return array_keys(array_intersect($this->_pop3Cache('uidl'), $ids->ids));
1183:     }
1184: 
1185: }
1186: 
API documentation generated by ApiGen