<?php /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Mail * @subpackage Protocol * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * @category Zend * @package Zend_Mail * @subpackage Protocol * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Mail_Protocol_Pop3 { /** * Default timeout in seconds for initiating session */ const TIMEOUT_CONNECTION = 30; /** * saves if server supports top * @var null|bool */ public $hasTop = null; /** * socket to pop3 * @var null|resource */ protected $_socket; /** * greeting timestamp for apop * @var null|string */ protected $_timestamp; /** * Public constructor * * @param string $host hostname or IP address of POP3 server, if given connect() is called * @param int|null $port port of POP3 server, null for default (110 or 995 for ssl) * @param bool|string $ssl use ssl? 'SSL', 'TLS' or false * @throws Zend_Mail_Protocol_Exception */ public function __construct($host = '', $port = null, $ssl = false) { if ($host) { $this->connect($host, $port, $ssl); } } /** * Public destructor */ public function __destruct() { $this->logout(); } /** * Open connection to POP3 server * * @param string $host hostname or IP address of POP3 server * @param int|null $port of POP3 server, default is 110 (995 for ssl) * @param string|bool $ssl use 'SSL', 'TLS' or false * @return string welcome message * @throws Zend_Mail_Protocol_Exception */ public function connect($host, $port = null, $ssl = false) { if ($ssl == 'SSL') { $host = 'ssl://' . $host; } if ($port === null) { $port = $ssl == 'SSL' ? 995 : 110; } $errno = 0; $errstr = ''; $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION); if (!$this->_socket) { /** * @see Zend_Mail_Protocol_Exception */ require_once 'Zend/Mail/Protocol/Exception.php'; throw new Zend_Mail_Protocol_Exception('cannot connect to host; error = ' . $errstr . ' (errno = ' . $errno . ' )'); } $welcome = $this->readResponse(); strtok($welcome, '<'); $this->_timestamp = strtok('>'); if (!strpos($this->_timestamp, '@')) { $this->_timestamp = null; } else { $this->_timestamp = '<' . $this->_timestamp . '>'; } if ($ssl === 'TLS') { $this->request('STLS'); $result = stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); if (!$result) { /** * @see Zend_Mail_Protocol_Exception */ require_once 'Zend/Mail/Protocol/Exception.php'; throw new Zend_Mail_Protocol_Exception('cannot enable TLS'); } } return $welcome; } /** * Send a request * * @param string $request your request without newline * @return null * @throws Zend_Mail_Protocol_Exception */ public function sendRequest($request) { $result = @fputs($this->_socket, $request . "\r\n"); if (!$result) { /** * @see Zend_Mail_Protocol_Exception */ require_once 'Zend/Mail/Protocol/Exception.php'; throw new Zend_Mail_Protocol_Exception('send failed - connection closed?'); } } /** * read a response * * @param boolean $multiline response has multiple lines and should be read until "<nl>.<nl>" * @return string response * @throws Zend_Mail_Protocol_Exception */ public function readResponse($multiline = false) { $result = @fgets($this->_socket); if (!is_string($result)) { /** * @see Zend_Mail_Protocol_Exception */ require_once 'Zend/Mail/Protocol/Exception.php'; throw new Zend_Mail_Protocol_Exception('read failed - connection closed?'); } $result = trim($result); if (strpos($result, ' ')) { list($status, $message) = explode(' ', $result, 2); } else { $status = $result; $message = ''; } if ($status != '+OK') { /** * @see Zend_Mail_Protocol_Exception */ require_once 'Zend/Mail/Protocol/Exception.php'; throw new Zend_Mail_Protocol_Exception('last request failed'); } if ($multiline) { $message = ''; $line = fgets($this->_socket); while ($line && rtrim($line, "\r\n") != '.') { if ($line[0] == '.') { $line = substr($line, 1); } $message .= $line; $line = fgets($this->_socket); }; } return $message; } /** * Send request and get resposne * * @see sendRequest(), readResponse() * * @param string $request request * @param bool $multiline multiline response? * @return string result from readResponse() * @throws Zend_Mail_Protocol_Exception */ public function request($request, $multiline = false) { $this->sendRequest($request); return $this->readResponse($multiline); } /** * End communication with POP3 server (also closes socket) * * @return null */ public function logout() { if (!$this->_socket) { return; } try { $this->request('QUIT'); } catch (Zend_Mail_Protocol_Exception $e) { // ignore error - we're closing the socket anyway } fclose($this->_socket); $this->_socket = null; } /** * Get capabilities from POP3 server * * @return array list of capabilities * @throws Zend_Mail_Protocol_Exception */ public function capa() { $result = $this->request('CAPA', true); return explode("\n", $result); } /** * Login to POP3 server. Can use APOP * * @param string $user username * @param string $password password * @param bool $try_apop should APOP be tried? * @return void * @throws Zend_Mail_Protocol_Exception */ public function login($user, $password, $tryApop = true) { if ($tryApop && $this->_timestamp) { try { $this->request("APOP $user " . md5($this->_timestamp . $password)); return; } catch (Zend_Mail_Protocol_Exception $e) { // ignore } } $result = $this->request("USER $user"); $result = $this->request("PASS $password"); } /** * Make STAT call for message count and size sum * * @param int $messages out parameter with count of messages * @param int $octets out parameter with size in octects of messages * @return void * @throws Zend_Mail_Protocol_Exception */ public function status(&$messages, &$octets) { $messages = 0; $octets = 0; $result = $this->request('STAT'); list($messages, $octets) = explode(' ', $result); } /** * Make LIST call for size of message(s) * * @param int|null $msgno number of message, null for all * @return int|array size of given message or list with array(num => size) * @throws Zend_Mail_Protocol_Exception */ public function getList($msgno = null) { if ($msgno !== null) { $result = $this->request("LIST $msgno"); list(, $result) = explode(' ', $result); return (int)$result; } $result = $this->request('LIST', true); $messages = array(); $line = strtok($result, "\n"); while ($line) { list($no, $size) = explode(' ', trim($line)); $messages[(int)$no] = (int)$size; $line = strtok("\n"); } return $messages; } /** * Make UIDL call for getting a uniqueid * * @param int|null $msgno number of message, null for all * @return string|array uniqueid of message or list with array(num => uniqueid) * @throws Zend_Mail_Protocol_Exception */ public function uniqueid($msgno = null) { if ($msgno !== null) { $result = $this->request("UIDL $msgno"); list(, $result) = explode(' ', $result); return $result; } $result = $this->request('UIDL', true); $result = explode("\n", $result); $messages = array(); foreach ($result as $line) { if (!$line) { continue; } list($no, $id) = explode(' ', trim($line), 2); $messages[(int)$no] = $id; } return $messages; } /** * Make TOP call for getting headers and maybe some body lines * This method also sets hasTop - before it it's not known if top is supported * * The fallback makes normale RETR call, which retrieves the whole message. Additional * lines are not removed. * * @param int $msgno number of message * @param int $lines number of wanted body lines (empty line is inserted after header lines) * @param bool $fallback fallback with full retrieve if top is not supported * @return string message headers with wanted body lines * @throws Zend_Mail_Protocol_Exception */ public function top($msgno, $lines = 0, $fallback = false) { if ($this->hasTop === false) { if ($fallback) { return $this->retrieve($msgno); } else { /** * @see Zend_Mail_Protocol_Exception */ require_once 'Zend/Mail/Protocol/Exception.php'; throw new Zend_Mail_Protocol_Exception('top not supported and no fallback wanted'); } } $this->hasTop = true; $lines = (!$lines || $lines < 1) ? 0 : (int)$lines; try { $result = $this->request("TOP $msgno $lines", true); } catch (Zend_Mail_Protocol_Exception $e) { $this->hasTop = false; if ($fallback) { $result = $this->retrieve($msgno); } else { throw $e; } } return $result; } /** * Make a RETR call for retrieving a full message with headers and body * * @deprecated since 1.1.0; this method has a typo - please use retrieve() * @param int $msgno message number * @return string message * @throws Zend_Mail_Protocol_Exception */ public function retrive($msgno) { return $this->retrieve($msgno); } /** * Make a RETR call for retrieving a full message with headers and body * * @param int $msgno message number * @return string message * @throws Zend_Mail_Protocol_Exception */ public function retrieve($msgno) { $result = $this->request("RETR $msgno", true); return $result; } /** * Make a NOOP call, maybe needed for keeping the server happy * * @return null * @throws Zend_Mail_Protocol_Exception */ public function noop() { $this->request('NOOP'); } /** * Make a DELE count to remove a message * * @return null * @throws Zend_Mail_Protocol_Exception */ public function delete($msgno) { $this->request("DELE $msgno"); } /** * Make RSET call, which rollbacks delete requests * * @return null * @throws Zend_Mail_Protocol_Exception */ public function undelete() { $this->request('RSET'); } }