<?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-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: Pop3.php 24594 2012-01-05 21:27:01Z matthew $
*/
/**
* @category Zend
* @package Zend_Mail
* @subpackage Protocol
* @copyright Copyright (c) 2005-2012 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');
}
}