summaryrefslogtreecommitdiff
path: root/lib/Zend/Mail/Protocol
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Zend/Mail/Protocol')
-rw-r--r--lib/Zend/Mail/Protocol/Abstract.php385
-rw-r--r--lib/Zend/Mail/Protocol/Exception.php39
-rw-r--r--lib/Zend/Mail/Protocol/Imap.php837
-rw-r--r--lib/Zend/Mail/Protocol/Pop3.php471
-rw-r--r--lib/Zend/Mail/Protocol/Smtp.php443
-rw-r--r--lib/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php108
-rw-r--r--lib/Zend/Mail/Protocol/Smtp/Auth/Login.php98
-rw-r--r--lib/Zend/Mail/Protocol/Smtp/Auth/Plain.php96
8 files changed, 2477 insertions, 0 deletions
diff --git a/lib/Zend/Mail/Protocol/Abstract.php b/lib/Zend/Mail/Protocol/Abstract.php
new file mode 100644
index 0000000..cfb1f42
--- /dev/null
+++ b/lib/Zend/Mail/Protocol/Abstract.php
@@ -0,0 +1,385 @@
+<?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-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Abstract.php 12395 2008-11-07 23:58:38Z nico $
+ */
+
+
+/**
+ * @see Zend_Validate
+ */
+require_once 'Zend/Validate.php';
+
+
+/**
+ * @see Zend_Validate_Hostname
+ */
+require_once 'Zend/Validate/Hostname.php';
+
+
+/**
+ * Zend_Mail_Protocol_Abstract
+ *
+ * Provides low-level methods for concrete adapters to communicate with a remote mail server and track requests and responses.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Abstract.php 12395 2008-11-07 23:58:38Z nico $
+ * @todo Implement proxy settings
+ */
+abstract class Zend_Mail_Protocol_Abstract
+{
+ /**
+ * Mail default EOL string
+ */
+ const EOL = "\r\n";
+
+
+ /**
+ * Default timeout in seconds for initiating session
+ */
+ const TIMEOUT_CONNECTION = 30;
+
+
+ /**
+ * Hostname or IP address of remote server
+ * @var string
+ */
+ protected $_host;
+
+
+ /**
+ * Port number of connection
+ * @var integer
+ */
+ protected $_port;
+
+
+ /**
+ * Instance of Zend_Validate to check hostnames
+ * @var Zend_Validate
+ */
+ protected $_validHost;
+
+
+ /**
+ * Socket connection resource
+ * @var resource
+ */
+ protected $_socket;
+
+
+ /**
+ * Last request sent to server
+ * @var string
+ */
+ protected $_request;
+
+
+ /**
+ * Array of server responses to last request
+ * @var array
+ */
+ protected $_response;
+
+
+ /**
+ * String template for parsing server responses using sscanf (default: 3 digit code and response string)
+ * @var resource
+ */
+ protected $_template = '%d%s';
+
+
+ /**
+ * Log of mail requests and server responses for a session
+ * @var string
+ */
+ private $_log;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host OPTIONAL Hostname of remote connection (default: 127.0.0.1)
+ * @param integer $port OPTIONAL Port number (default: null)
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', $port = null)
+ {
+ $this->_validHost = new Zend_Validate();
+ $this->_validHost->addValidator(new Zend_Validate_Hostname(Zend_Validate_Hostname::ALLOW_ALL));
+
+ if (!$this->_validHost->isValid($host)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception(join(', ', $this->_validHost->getMessages()));
+ }
+
+ $this->_host = $host;
+ $this->_port = $port;
+ }
+
+
+ /**
+ * Class destructor to cleanup open resources
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ $this->_disconnect();
+ }
+
+
+ /**
+ * Create a connection to the remote host
+ *
+ * Concrete adapters for this class will implement their own unique connect scripts, using the _connect() method to create the socket resource.
+ */
+ abstract public function connect();
+
+
+ /**
+ * Retrieve the last client request
+ *
+ * @return string
+ */
+ public function getRequest()
+ {
+ return $this->_request;
+ }
+
+
+ /**
+ * Retrieve the last server response
+ *
+ * @return array
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+
+ /**
+ * Retrieve the transaction log
+ *
+ * @return string
+ */
+ public function getLog()
+ {
+ return $this->_log;
+ }
+
+
+ /**
+ * Reset the transaction log
+ *
+ * @return void
+ */
+ public function resetLog()
+ {
+ $this->_log = '';
+ }
+
+
+ /**
+ * Connect to the server using the supplied transport and target
+ *
+ * An example $remote string may be 'tcp://mail.example.com:25' or 'ssh://hostname.com:2222'
+ *
+ * @param string $remote Remote
+ * @throws Zend_Mail_Protocol_Exception
+ * @return boolean
+ */
+ protected function _connect($remote)
+ {
+ $errorNum = 0;
+ $errorStr = '';
+
+ // open connection
+ $this->_socket = @stream_socket_client($remote, $errorNum, $errorStr, self::TIMEOUT_CONNECTION);
+
+ if ($this->_socket === false) {
+ if ($errorNum == 0) {
+ $errorStr = 'Could not open socket';
+ }
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception($errorStr);
+ }
+
+ if (($result = stream_set_timeout($this->_socket, self::TIMEOUT_CONNECTION)) === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('Could not set stream timeout');
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Disconnect from remote host and free resource
+ *
+ * @return void
+ */
+ protected function _disconnect()
+ {
+ if (is_resource($this->_socket)) {
+ fclose($this->_socket);
+ }
+ }
+
+
+ /**
+ * Send the given request followed by a LINEEND to the server.
+ *
+ * @param string $request
+ * @throws Zend_Mail_Protocol_Exception
+ * @return integer|boolean Number of bytes written to remote host
+ */
+ protected function _send($request)
+ {
+ if (!is_resource($this->_socket)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('No connection has been established to ' . $this->_host);
+ }
+
+ $this->_request = $request;
+
+ $result = fwrite($this->_socket, $request . self::EOL);
+
+ // Save request to internal log
+ $this->_log .= $request . self::EOL;
+
+ if ($result === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('Could not send request to ' . $this->_host);
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Get a line from the stream.
+ *
+ * @var integer $timeout Per-request timeout value if applicable
+ * @throws Zend_Mail_Protocol_Exception
+ * @return string
+ */
+ protected function _receive($timeout = null)
+ {
+ if (!is_resource($this->_socket)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('No connection has been established to ' . $this->_host);
+ }
+
+ // Adapters may wish to supply per-commend timeouts according to appropriate RFC
+ if ($timeout !== null) {
+ stream_set_timeout($this->_socket, $timeout);
+ }
+
+ // Retrieve response
+ $reponse = fgets($this->_socket, 1024);
+
+ // Save request to internal log
+ $this->_log .= $reponse;
+
+ // Check meta data to ensure connection is still valid
+ $info = stream_get_meta_data($this->_socket);
+
+ if (!empty($info['timed_out'])) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception($this->_host . ' has timed out');
+ }
+
+ if ($reponse === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('Could not read from ' . $this->_host);
+ }
+
+ return $reponse;
+ }
+
+
+ /**
+ * Parse server response for successful codes
+ *
+ * Read the response from the stream and check for expected return code.
+ * Throws a Zend_Mail_Protocol_Exception if an unexpected code is returned.
+ *
+ * @param string|array $code One or more codes that indicate a successful response
+ * @throws Zend_Mail_Protocol_Exception
+ * @return string Last line of response string
+ */
+ protected function _expect($code, $timeout = null)
+ {
+ $this->_response = array();
+ $cmd = '';
+ $msg = '';
+
+ if (!is_array($code)) {
+ $code = array($code);
+ }
+
+ do {
+ $this->_response[] = $result = $this->_receive($timeout);
+ sscanf($result, $this->_template, $cmd, $msg);
+
+ if ($cmd === null || !in_array($cmd, $code)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception($result);
+ }
+
+ } while (strpos($msg, '-') === 0); // The '-' message prefix indicates an information string instead of a response string.
+
+ return $msg;
+ }
+}
diff --git a/lib/Zend/Mail/Protocol/Exception.php b/lib/Zend/Mail/Protocol/Exception.php
new file mode 100644
index 0000000..c3add11
--- /dev/null
+++ b/lib/Zend/Mail/Protocol/Exception.php
@@ -0,0 +1,39 @@
+<?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-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Exception.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @see Zend_Exception
+ */
+require_once 'Zend/Mail/Exception.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Exception extends Zend_Mail_Exception
+{}
+
diff --git a/lib/Zend/Mail/Protocol/Imap.php b/lib/Zend/Mail/Protocol/Imap.php
new file mode 100644
index 0000000..eec0ff3
--- /dev/null
+++ b/lib/Zend/Mail/Protocol/Imap.php
@@ -0,0 +1,837 @@
+<?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-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Imap.php 12539 2008-11-11 02:47:17Z yoshida@zend.co.jp $
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Imap
+{
+ /**
+ * Default timeout in seconds for initiating session
+ */
+ const TIMEOUT_CONNECTION = 30;
+
+ /**
+ * socket to imap server
+ * @var resource|null
+ */
+ protected $_socket;
+
+ /**
+ * counter for request tag
+ * @var int
+ */
+ protected $_tagCount = 0;
+
+ /**
+ * Public constructor
+ *
+ * @param string $host hostname of IP address of IMAP server, if given connect() is called
+ * @param int|null $port port of IMAP server, null for default (143 or 993 for ssl)
+ * @param bool $ssl use ssl? 'SSL', 'TLS' or false
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ 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 of IP address of POP3 server
+ * @param int|null $port of IMAP server, default is 143 (993 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' ? 993 : 143;
+ }
+
+ $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 : ' . $errno . ' : ' . $errstr);
+ }
+
+ if (!$this->_assumedNextLine('* OK')) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('host doesn\'t allow connection');
+ }
+
+ if ($ssl === 'TLS') {
+ $result = $this->requestAndResponse('STARTTLS');
+ $result = $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');
+ }
+ }
+ }
+
+ /**
+ * get the next line from socket with error checking, but nothing else
+ *
+ * @return string next line
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ protected function _nextLine()
+ {
+ $line = @fgets($this->_socket);
+ if ($line === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('cannot read - connection closed?');
+ }
+
+ return $line;
+ }
+
+ /**
+ * get next line and assume it starts with $start. some requests give a simple
+ * feedback so we can quickly check if we can go on.
+ *
+ * @param string $start the first bytes we assume to be in the next line
+ * @return bool line starts with $start
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ protected function _assumedNextLine($start)
+ {
+ $line = $this->_nextLine();
+ return strpos($line, $start) === 0;
+ }
+
+ /**
+ * get next line and split the tag. that's the normal case for a response line
+ *
+ * @param string $tag tag of line is returned by reference
+ * @return string next line
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ protected function _nextTaggedLine(&$tag)
+ {
+ $line = $this->_nextLine();
+
+ // seperate tag from line
+ list($tag, $line) = explode(' ', $line, 2);
+
+ return $line;
+ }
+
+ /**
+ * split a given line in tokens. a token is literal of any form or a list
+ *
+ * @param string $line line to decode
+ * @return array tokens, literals are returned as string, lists as array
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ protected function _decodeLine($line)
+ {
+ $tokens = array();
+ $stack = array();
+
+ /*
+ We start to decode the response here. The unterstood tokens are:
+ literal
+ "literal" or also "lit\\er\"al"
+ {bytes}<NL>literal
+ (literals*)
+ All tokens are returned in an array. Literals in braces (the last unterstood
+ token in the list) are returned as an array of tokens. I.e. the following response:
+ "foo" baz {3}<NL>bar ("f\\\"oo" bar)
+ would be returned as:
+ array('foo', 'baz', 'bar', array('f\\\"oo', 'bar'));
+
+ // TODO: add handling of '[' and ']' to parser for easier handling of response text
+ */
+ // replace any trailling <NL> including spaces with a single space
+ $line = rtrim($line) . ' ';
+ while (($pos = strpos($line, ' ')) !== false) {
+ $token = substr($line, 0, $pos);
+ while ($token[0] == '(') {
+ array_push($stack, $tokens);
+ $tokens = array();
+ $token = substr($token, 1);
+ }
+ if ($token[0] == '"') {
+ if (preg_match('%^"((.|\\\\|\\")*?)" *%', $line, $matches)) {
+ $tokens[] = $matches[1];
+ $line = substr($line, strlen($matches[0]));
+ continue;
+ }
+ }
+ if ($token[0] == '{') {
+ $endPos = strpos($token, '}');
+ $chars = substr($token, 1, $endPos - 1);
+ if (is_numeric($chars)) {
+ $token = '';
+ while (strlen($token) < $chars) {
+ $token .= $this->_nextLine();
+ }
+ $line = '';
+ if (strlen($token) > $chars) {
+ $line = substr($token, $chars);
+ $token = substr($token, 0, $chars);
+ } else {
+ $line .= $this->_nextLine();
+ }
+ $tokens[] = $token;
+ $line = trim($line) . ' ';
+ continue;
+ }
+ }
+ if ($stack && $token[strlen($token) - 1] == ')') {
+ // closing braces are not seperated by spaces, so we need to count them
+ $braces = strlen($token);
+ $token = rtrim($token, ')');
+ // only count braces if more than one
+ $braces -= strlen($token) + 1;
+ // only add if token had more than just closing braces
+ if ($token) {
+ $tokens[] = $token;
+ }
+ $token = $tokens;
+ $tokens = array_pop($stack);
+ // special handline if more than one closing brace
+ while ($braces-- > 0) {
+ $tokens[] = $token;
+ $token = $tokens;
+ $tokens = array_pop($stack);
+ }
+ }
+ $tokens[] = $token;
+ $line = substr($line, $pos + 1);
+ }
+
+ // maybe the server forgot to send some closing braces
+ while ($stack) {
+ $child = $tokens;
+ $tokens = array_pop($stack);
+ $tokens[] = $child;
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * read a response "line" (could also be more than one real line if response has {..}<NL>)
+ * and do a simple decode
+ *
+ * @param array|string $tokens decoded tokens are returned by reference, if $dontParse
+ * is true the unparsed line is returned here
+ * @param string $wantedTag check for this tag for response code. Default '*' is
+ * continuation tag.
+ * @param bool $dontParse if true only the unparsed line is returned $tokens
+ * @return bool if returned tag matches wanted tag
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function readLine(&$tokens = array(), $wantedTag = '*', $dontParse = false)
+ {
+ $line = $this->_nextTaggedLine($tag);
+ if (!$dontParse) {
+ $tokens = $this->_decodeLine($line);
+ } else {
+ $tokens = $line;
+ }
+
+ // if tag is wanted tag we might be at the end of a multiline response
+ return $tag == $wantedTag;
+ }
+
+ /**
+ * read all lines of response until given tag is found (last line of response)
+ *
+ * @param string $tag the tag of your request
+ * @param string|array $filter you can filter the response so you get only the
+ * given response lines
+ * @param bool $dontParse if true every line is returned unparsed instead of
+ * the decoded tokens
+ * @return null|bool|array tokens if success, false if error, null if bad request
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function readResponse($tag, $dontParse = false)
+ {
+ $lines = array();
+ while (!$this->readLine($tokens, $tag, $dontParse)) {
+ $lines[] = $tokens;
+ }
+
+ if ($dontParse) {
+ // last to chars are still needed for response code
+ $tokens = array(substr($tokens, 0, 2));
+ }
+ // last line has response code
+ if ($tokens[0] == 'OK') {
+ return $lines ? $lines : true;
+ } else if ($tokens[0] == 'NO'){
+ return false;
+ }
+ return null;
+ }
+
+ /**
+ * send a request
+ *
+ * @param string $command your request command
+ * @param array $tokens additional parameters to command, use escapeString() to prepare
+ * @param string $tag provide a tag otherwise an autogenerated is returned
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function sendRequest($command, $tokens = array(), &$tag = null)
+ {
+ if (!$tag) {
+ ++$this->_tagCount;
+ $tag = 'TAG' . $this->_tagCount;
+ }
+
+ $line = $tag . ' ' . $command;
+
+ foreach ($tokens as $token) {
+ if (is_array($token)) {
+ if (@fputs($this->_socket, $line . ' ' . $token[0] . "\r\n") === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
+ }
+ if (!$this->_assumedNextLine('+ ')) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('cannot send literal string');
+ }
+ $line = $token[1];
+ } else {
+ $line .= ' ' . $token;
+ }
+ }
+
+ if (@fputs($this->_socket, $line . "\r\n") === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
+ }
+ }
+
+ /**
+ * send a request and get response at once
+ *
+ * @param string $command command as in sendRequest()
+ * @param array $tokens parameters as in sendRequest()
+ * @param bool $dontParse if true unparsed lines are returned instead of tokens
+ * @return mixed response as in readResponse()
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function requestAndResponse($command, $tokens = array(), $dontParse = false)
+ {
+ $this->sendRequest($command, $tokens, $tag);
+ $response = $this->readResponse($tag, $dontParse);
+
+ return $response;
+ }
+
+ /**
+ * escape one or more literals i.e. for sendRequest
+ *
+ * @param string|array $string the literal/-s
+ * @return string|array escape literals, literals with newline ar returned
+ * as array('{size}', 'string');
+ */
+ public function escapeString($string)
+ {
+ if (func_num_args() < 2) {
+ if (strpos($string, "\n") !== false) {
+ return array('{' . strlen($string) . '}', $string);
+ } else {
+ return '"' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $string) . '"';
+ }
+ }
+ $result = array();
+ foreach (func_get_args() as $string) {
+ $result[] = $this->escapeString($string);
+ }
+ return $result;
+ }
+
+ /**
+ * escape a list with literals or lists
+ *
+ * @param array $list list with literals or lists as PHP array
+ * @return string escaped list for imap
+ */
+ public function escapeList($list)
+ {
+ $result = array();
+ foreach ($list as $k => $v) {
+ if (!is_array($v)) {
+// $result[] = $this->escapeString($v);
+ $result[] = $v;
+ continue;
+ }
+ $result[] = $this->escapeList($v);
+ }
+ return '(' . implode(' ', $result) . ')';
+ }
+
+ /**
+ * Login to IMAP server.
+ *
+ * @param string $user username
+ * @param string $password password
+ * @return bool success
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function login($user, $password)
+ {
+ return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true);
+ }
+
+ /**
+ * logout of imap server
+ *
+ * @return bool success
+ */
+ public function logout()
+ {
+ $result = false;
+ if ($this->_socket) {
+ try {
+ $result = $this->requestAndResponse('LOGOUT', array(), true);
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ // ignoring exception
+ }
+ fclose($this->_socket);
+ $this->_socket = null;
+ }
+ return $result;
+ }
+
+
+ /**
+ * Get capabilities from IMAP server
+ *
+ * @return array list of capabilities
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function capability()
+ {
+ $response = $this->requestAndResponse('CAPABILITY');
+
+ if (!$response) {
+ return $response;
+ }
+
+ $capabilities = array();
+ foreach ($response as $line) {
+ $capabilities = array_merge($capabilities, $line);
+ }
+ return $capabilities;
+ }
+
+ /**
+ * Examine and select have the same response. The common code for both
+ * is in this method
+ *
+ * @param string $command can be 'EXAMINE' or 'SELECT' and this is used as command
+ * @param string $box which folder to change to or examine
+ * @return bool|array false if error, array with returned information
+ * otherwise (flags, exists, recent, uidvalidity)
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX')
+ {
+ $this->sendRequest($command, array($this->escapeString($box)), $tag);
+
+ $result = array();
+ while (!$this->readLine($tokens, $tag)) {
+ if ($tokens[0] == 'FLAGS') {
+ array_shift($tokens);
+ $result['flags'] = $tokens;
+ continue;
+ }
+ switch ($tokens[1]) {
+ case 'EXISTS':
+ case 'RECENT':
+ $result[strtolower($tokens[1])] = $tokens[0];
+ break;
+ case '[UIDVALIDITY':
+ $result['uidvalidity'] = (int)$tokens[2];
+ break;
+ default:
+ // ignore
+ }
+ }
+
+ if ($tokens[0] != 'OK') {
+ return false;
+ }
+ return $result;
+ }
+
+ /**
+ * change folder
+ *
+ * @param string $box change to this folder
+ * @return bool|array see examineOrselect()
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function select($box = 'INBOX')
+ {
+ return $this->examineOrSelect('SELECT', $box);
+ }
+
+ /**
+ * examine folder
+ *
+ * @param string $box examine this folder
+ * @return bool|array see examineOrselect()
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function examine($box = 'INBOX')
+ {
+ return $this->examineOrSelect('EXAMINE', $box);
+ }
+
+ /**
+ * fetch one or more items of one or more messages
+ *
+ * @param string|array $items items to fetch from message(s) as string (if only one item)
+ * or array of strings
+ * @param int $from message for items or start message if $to !== null
+ * @param int|null $to if null only one message ($from) is fetched, else it's the
+ * last message, INF means last message avaible
+ * @return string|array if only one item of one message is fetched it's returned as string
+ * if items of one message are fetched it's returned as (name => value)
+ * if one items of messages are fetched it's returned as (msgno => value)
+ * if items of messages are fetchted it's returned as (msgno => (name => value))
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function fetch($items, $from, $to = null)
+ {
+ if (is_array($from)) {
+ $set = implode(',', $from);
+ } else if ($to === null) {
+ $set = (int)$from;
+ } else if ($to === INF) {
+ $set = (int)$from . ':*';
+ } else {
+ $set = (int)$from . ':' . (int)$to;
+ }
+
+ $items = (array)$items;
+ $itemList = $this->escapeList($items);
+
+ $this->sendRequest('FETCH', array($set, $itemList), $tag);
+
+ $result = array();
+ while (!$this->readLine($tokens, $tag)) {
+ // ignore other responses
+ if ($tokens[1] != 'FETCH') {
+ continue;
+ }
+ // ignore other messages
+ if ($to === null && !is_array($from) && $tokens[0] != $from) {
+ continue;
+ }
+ // if we only want one item we return that one directly
+ if (count($items) == 1) {
+ if ($tokens[2][0] == $items[0]) {
+ $data = $tokens[2][1];
+ } else {
+ // maybe the server send an other field we didn't wanted
+ $count = count($tokens[2]);
+ // we start with 2, because 0 was already checked
+ for ($i = 2; $i < $count; $i += 2) {
+ if ($tokens[2][$i] != $items[0]) {
+ continue;
+ }
+ $data = $tokens[2][$i + 1];
+ break;
+ }
+ }
+ } else {
+ $data = array();
+ while (key($tokens[2]) !== null) {
+ $data[current($tokens[2])] = next($tokens[2]);
+ next($tokens[2]);
+ }
+ }
+ // if we want only one message we can ignore everything else and just return
+ if ($to === null && !is_array($from) && $tokens[0] == $from) {
+ // we still need to read all lines
+ while (!$this->readLine($tokens, $tag));
+ return $data;
+ }
+ $result[$tokens[0]] = $data;
+ }
+
+ if ($to === null && !is_array($from)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('the single id was not found in response');
+ }
+
+ return $result;
+ }
+
+ /**
+ * get mailbox list
+ *
+ * this method can't be named after the IMAP command 'LIST', as list is a reserved keyword
+ *
+ * @param string $reference mailbox reference for list
+ * @param string $mailbox mailbox name match with wildcards
+ * @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..))
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function listMailbox($reference = '', $mailbox = '*')
+ {
+ $result = array();
+ $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox));
+ if (!$list || $list === true) {
+ return $result;
+ }
+
+ foreach ($list as $item) {
+ if (count($item) != 4 || $item[0] != 'LIST') {
+ continue;
+ }
+ $result[$item[3]] = array('delim' => $item[2], 'flags' => $item[1]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * set flags
+ *
+ * @param array $flags flags to set, add or remove - see $mode
+ * @param int $from message for items or start message if $to !== null
+ * @param int|null $to if null only one message ($from) is fetched, else it's the
+ * last message, INF means last message avaible
+ * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given
+ * @param bool $silent if false the return values are the new flags for the wanted messages
+ * @return bool|array new flags if $silent is false, else true or false depending on success
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function store(array $flags, $from, $to = null, $mode = null, $silent = true)
+ {
+ $item = 'FLAGS';
+ if ($mode == '+' || $mode == '-') {
+ $item = $mode . $item;
+ }
+ if ($silent) {
+ $item .= '.SILENT';
+ }
+
+ $flags = $this->escapeList($flags);
+ $set = (int)$from;
+ if ($to != null) {
+ $set .= ':' . ($to == INF ? '*' : (int)$to);
+ }
+
+ $result = $this->requestAndResponse('STORE', array($set, $item, $flags), $silent);
+
+ if ($silent) {
+ return $result ? true : false;
+ }
+
+ $tokens = $result;
+ $result = array();
+ foreach ($tokens as $token) {
+ if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') {
+ continue;
+ }
+ $result[$token[0]] = $token[2][1];
+ }
+
+ return $result;
+ }
+
+ /**
+ * append a new message to given folder
+ *
+ * @param string $folder name of target folder
+ * @param string $message full message content
+ * @param array $flags flags for new message
+ * @param string $date date for new message
+ * @return bool success
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function append($folder, $message, $flags = null, $date = null)
+ {
+ $tokens = array();
+ $tokens[] = $this->escapeString($folder);
+ if ($flags !== null) {
+ $tokens[] = $this->escapeList($flags);
+ }
+ if ($date !== null) {
+ $tokens[] = $this->escapeString($date);
+ }
+ $tokens[] = $this->escapeString($message);
+
+ return $this->requestAndResponse('APPEND', $tokens, true);
+ }
+
+ /**
+ * copy message set from current folder to other folder
+ *
+ * @param string $folder destination folder
+ * @param int|null $to if null only one message ($from) is fetched, else it's the
+ * last message, INF means last message avaible
+ * @return bool success
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function copy($folder, $from, $to = null)
+ {
+ $set = (int)$from;
+ if ($to != null) {
+ $set .= ':' . ($to == INF ? '*' : (int)$to);
+ }
+
+ return $this->requestAndResponse('COPY', array($set, $this->escapeString($folder)), true);
+ }
+
+ /**
+ * create a new folder (and parent folders if needed)
+ *
+ * @param string $folder folder name
+ * @return bool success
+ */
+ public function create($folder)
+ {
+ return $this->requestAndResponse('CREATE', array($this->escapeString($folder)), true);
+ }
+
+ /**
+ * rename an existing folder
+ *
+ * @param string $old old name
+ * @param string $new new name
+ * @return bool success
+ */
+ public function rename($old, $new)
+ {
+ return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true);
+ }
+
+ /**
+ * remove a folder
+ *
+ * @param string $folder folder name
+ * @return bool success
+ */
+ public function delete($folder)
+ {
+ return $this->requestAndResponse('DELETE', array($this->escapeString($folder)), true);
+ }
+
+ /**
+ * permanently remove messages
+ *
+ * @return bool success
+ */
+ public function expunge()
+ {
+ // TODO: parse response?
+ return $this->requestAndResponse('EXPUNGE');
+ }
+
+ /**
+ * send noop
+ *
+ * @return bool success
+ */
+ public function noop()
+ {
+ // TODO: parse response
+ return $this->requestAndResponse('NOOP');
+ }
+
+ /**
+ * do a search request
+ *
+ * This method is currently marked as internal as the API might change and is not
+ * safe if you don't take precautions.
+ *
+ * @internal
+ * @return array message ids
+ */
+ public function search(array $params)
+ {
+ $response = $this->requestAndResponse('SEARCH', $params);
+ if (!$response) {
+ return $response;
+ }
+
+ foreach ($response as $ids) {
+ if ($ids[0] == 'SEARCH') {
+ array_shift($ids);
+ return $ids;
+ }
+ }
+ return array();
+ }
+
+}
diff --git a/lib/Zend/Mail/Protocol/Pop3.php b/lib/Zend/Mail/Protocol/Pop3.php
new file mode 100644
index 0000000..c1f968a
--- /dev/null
+++ b/lib/Zend/Mail/Protocol/Pop3.php
@@ -0,0 +1,471 @@
+<?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-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Pop3.php 12539 2008-11-11 02:47:17Z yoshida@zend.co.jp $
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2008 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 of 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 of 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 : ' . $errno . ' : ' . $errstr);
+ }
+
+ $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');
+ }
+}
diff --git a/lib/Zend/Mail/Protocol/Smtp.php b/lib/Zend/Mail/Protocol/Smtp.php
new file mode 100644
index 0000000..18ac77a
--- /dev/null
+++ b/lib/Zend/Mail/Protocol/Smtp.php
@@ -0,0 +1,443 @@
+<?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-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Smtp.php 11196 2008-09-02 00:56:25Z yoshida@zend.co.jp $
+ */
+
+
+/**
+ * @see Zend_Mime
+ */
+require_once 'Zend/Mime.php';
+
+
+/**
+ * @see Zend_Mail_Protocol_Abstract
+ */
+require_once 'Zend/Mail/Protocol/Abstract.php';
+
+
+/**
+ * Smtp implementation of Zend_Mail_Protocol_Abstract
+ *
+ * Minimum implementation according to RFC2821: EHLO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, QUIT
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Smtp extends Zend_Mail_Protocol_Abstract
+{
+ /**
+ * The transport method for the socket
+ *
+ * @var string
+ */
+ protected $_transport = 'tcp';
+
+
+ /**
+ * Indicates that a session is requested to be secure
+ *
+ * @var string
+ */
+ protected $_secure;
+
+
+ /**
+ * Indicates an smtp session has been started by the HELO command
+ *
+ * @var boolean
+ */
+ protected $_sess = false;
+
+
+ /**
+ * Indicates the HELO command has been issues
+ *
+ * @var unknown_type
+ */
+ protected $_helo = false;
+
+
+ /**
+ * Indicates an smtp AUTH has been issued and authenticated
+ *
+ * @var unknown_type
+ */
+ protected $_auth = false;
+
+
+ /**
+ * Indicates a MAIL command has been issued
+ *
+ * @var unknown_type
+ */
+ protected $_mail = false;
+
+
+ /**
+ * Indicates one or more RCTP commands have been issued
+ *
+ * @var unknown_type
+ */
+ protected $_rcpt = false;
+
+
+ /**
+ * Indicates that DATA has been issued and sent
+ *
+ * @var unknown_type
+ */
+ protected $_data = null;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host
+ * @param integer $port
+ * @param array $config
+ * @return void
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function __construct($host = '127.0.0.1', $port = null, array $config = array())
+ {
+ if (isset($config['ssl'])) {
+ switch (strtolower($config['ssl'])) {
+ case 'tls':
+ $this->_secure = 'tls';
+ break;
+
+ case 'ssl':
+ $this->_transport = 'ssl';
+ $this->_secure = 'ssl';
+ if ($port == null) {
+ $port = 465;
+ }
+ break;
+
+ default:
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception($config['ssl'] . ' is unsupported SSL type');
+ break;
+ }
+ }
+
+ // If no port has been specified then check the master PHP ini file. Defaults to 25 if the ini setting is null.
+ if ($port == null) {
+ if (($port = ini_get('smtp_port')) == '') {
+ $port = 25;
+ }
+ }
+
+ parent::__construct($host, $port);
+ }
+
+
+ /**
+ * Connect to the server with the parameters given in the constructor.
+ *
+ * @return boolean
+ */
+ public function connect()
+ {
+ return $this->_connect($this->_transport . '://' . $this->_host . ':'. $this->_port);
+ }
+
+
+ /**
+ * Initiate HELO/EHLO sequence and set flag to indicate valid smtp session
+ *
+ * @param string $host The client hostname or IP address (default: 127.0.0.1)
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function helo($host = '127.0.0.1')
+ {
+ // Respect RFC 2821 and disallow HELO attempts if session is already initiated.
+ if ($this->_sess === true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('Cannot issue HELO to existing session');
+ }
+
+ // Validate client hostname
+ if (!$this->_validHost->isValid($host)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception(join(', ', $this->_validHost->getMessages()));
+ }
+
+ // Initiate helo sequence
+ $this->_expect(220, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ $this->_ehlo($host);
+
+ // If a TLS session is required, commence negotiation
+ if ($this->_secure == 'tls') {
+ $this->_send('STARTTLS');
+ $this->_expect(220, 180);
+ if (!stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('Unable to connect via TLS');
+ }
+ $this->_ehlo($host);
+ }
+
+ $this->_startSession();
+ $this->auth();
+ }
+
+
+ /**
+ * Send EHLO or HELO depending on capabilities of smtp host
+ *
+ * @param string $host The client hostname or IP address (default: 127.0.0.1)
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ protected function _ehlo($host)
+ {
+ // Support for older, less-compliant remote servers. Tries multiple attempts of EHLO or HELO.
+ try {
+ $this->_send('EHLO ' . $host);
+ $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ $this->_send('HELO ' . $host);
+ $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ throw $e;
+ }
+ }
+
+
+ /**
+ * Issues MAIL command
+ *
+ * @param string $from Sender mailbox
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function mail($from)
+ {
+ if ($this->_sess !== true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('A valid session has not been started');
+ }
+
+ $this->_send('MAIL FROM:<' . $from . '>');
+ $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+
+ // Set mail to true, clear recipients and any existing data flags as per 4.1.1.2 of RFC 2821
+ $this->_mail = true;
+ $this->_rcpt = false;
+ $this->_data = false;
+ }
+
+
+ /**
+ * Issues RCPT command
+ *
+ * @param string $to Receiver(s) mailbox
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function rcpt($to)
+ {
+ if ($this->_mail !== true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('No sender reverse path has been supplied');
+ }
+
+ // Set rcpt to true, as per 4.1.1.3 of RFC 2821
+ $this->_send('RCPT TO:<' . $to . '>');
+ $this->_expect(array(250, 251), 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ $this->_rcpt = true;
+ }
+
+
+ /**
+ * Issues DATA command
+ *
+ * @param string $data
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function data($data)
+ {
+ // Ensure recipients have been set
+ if ($this->_rcpt !== true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('No recipient forward path has been supplied');
+ }
+
+ $this->_send('DATA');
+ $this->_expect(354, 120); // Timeout set for 2 minutes as per RFC 2821 4.5.3.2
+
+ foreach (explode(Zend_Mime::LINEEND, $data) as $line) {
+ if (strpos($line, '.') === 0) {
+ // Escape lines prefixed with a '.'
+ $line = '.' . $line;
+ }
+ $this->_send($line);
+ }
+
+ $this->_send('.');
+ $this->_expect(250, 600); // Timeout set for 10 minutes as per RFC 2821 4.5.3.2
+ $this->_data = true;
+ }
+
+
+ /**
+ * Issues the RSET command end validates answer
+ *
+ * Can be used to restore a clean smtp communication state when a transaction has been cancelled or commencing a new transaction.
+ *
+ * @return void
+ */
+ public function rset()
+ {
+ $this->_send('RSET');
+ // MS ESMTP doesn't follow RFC, see [ZF-1377]
+ $this->_expect(array(250, 220));
+
+ $this->_mail = false;
+ $this->_rcpt = false;
+ $this->_data = false;
+ }
+
+
+ /**
+ * Issues the NOOP command end validates answer
+ *
+ * Not used by Zend_Mail, could be used to keep a connection alive or check if it is still open.
+ *
+ * @return void
+ */
+ public function noop()
+ {
+ $this->_send('NOOP');
+ $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ }
+
+
+ /**
+ * Issues the VRFY command end validates answer
+ *
+ * Not used by Zend_Mail.
+ *
+ * @param string $user User Name or eMail to verify
+ * @return void
+ */
+ public function vrfy($user)
+ {
+ $this->_send('VRFY ' . $user);
+ $this->_expect(array(250, 251, 252), 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ }
+
+
+ /**
+ * Issues the QUIT command and clears the current session
+ *
+ * @return void
+ */
+ public function quit()
+ {
+ if ($this->_sess) {
+ $this->_send('QUIT');
+ $this->_expect(221, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ $this->_stopSession();
+ }
+ }
+
+
+ /**
+ * Default authentication method
+ *
+ * This default method is implemented by AUTH adapters to properly authenticate to a remote host.
+ *
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function auth()
+ {
+ if ($this->_auth === true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ require_once 'Zend/Mail/Protocol/Exception.php';
+ throw new Zend_Mail_Protocol_Exception('Already authenticated for this session');
+ }
+ }
+
+
+ /**
+ * Closes connection
+ *
+ * @return void
+ */
+ public function disconnect()
+ {
+ $this->_disconnect();
+ }
+
+
+ /**
+ * Start mail session
+ *
+ * @return void
+ */
+ protected function _startSession()
+ {
+ $this->_sess = true;
+ }
+
+
+ /**
+ * Stop mail session
+ *
+ * @return void
+ */
+ protected function _stopSession()
+ {
+ $this->_sess = false;
+ }
+}
diff --git a/lib/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php b/lib/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php
new file mode 100644
index 0000000..337c7eb
--- /dev/null
+++ b/lib/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php
@@ -0,0 +1,108 @@
+<?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-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Crammd5.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mail_Protocol_Smtp
+ */
+require_once 'Zend/Mail/Protocol/Smtp.php';
+
+
+/**
+ * Performs CRAM-MD5 authentication
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Smtp_Auth_Crammd5 extends Zend_Mail_Protocol_Smtp
+{
+ /**
+ * Constructor.
+ *
+ * @param string $host (Default: 127.0.0.1)
+ * @param int $port (Default: null)
+ * @param array $config Auth-specific parameters
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', $port = null, $config = null)
+ {
+ if (is_array($config)) {
+ if (isset($config['username'])) {
+ $this->_username = $config['username'];
+ }
+ if (isset($config['password'])) {
+ $this->_password = $config['password'];
+ }
+ }
+
+ parent::__construct($host, $port, $config);
+ }
+
+
+ /**
+ * @todo Perform CRAM-MD5 authentication with supplied credentials
+ *
+ * @return void
+ */
+ public function auth()
+ {
+ // Ensure AUTH has not already been initiated.
+ parent::auth();
+
+ $this->_send('AUTH CRAM-MD5');
+ $challenge = $this->_expect(334);
+ $challenge = base64_decode($challenge);
+ $digest = $this->_hmacMd5($this->_password, $challenge);
+ $this->_send(base64_encode($this->_username . ' ' . $digest));
+ $this->_expect(235);
+ $this->_auth = true;
+ }
+
+
+ /**
+ * Prepare CRAM-MD5 response to server's ticket
+ *
+ * @param string $key Challenge key (usually password)
+ * @param string $data Challenge data
+ * @param string $block Length of blocks
+ * @return string
+ */
+ protected function _hmacMd5($key, $data, $block = 64)
+ {
+ if (strlen($key) > 64) {
+ $key = pack('H32', md5($key));
+ } elseif (strlen($key) < 64) {
+ $key = str_pad($key, $block, chr(0));
+ }
+
+ $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
+ $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
+
+ $inner = pack('H32', md5($k_ipad . $data));
+ $digest = md5($k_opad . $inner);
+
+ return $digest;
+ }
+}
diff --git a/lib/Zend/Mail/Protocol/Smtp/Auth/Login.php b/lib/Zend/Mail/Protocol/Smtp/Auth/Login.php
new file mode 100644
index 0000000..6332207
--- /dev/null
+++ b/lib/Zend/Mail/Protocol/Smtp/Auth/Login.php
@@ -0,0 +1,98 @@
+<?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-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Login.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mail_Protocol_Smtp
+ */
+require_once 'Zend/Mail/Protocol/Smtp.php';
+
+
+/**
+ * Performs LOGIN authentication
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Smtp_Auth_Login extends Zend_Mail_Protocol_Smtp
+{
+ /**
+ * LOGIN username
+ *
+ * @var string
+ */
+ protected $_username;
+
+
+ /**
+ * LOGIN password
+ *
+ * @var string
+ */
+ protected $_password;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host (Default: 127.0.0.1)
+ * @param int $port (Default: null)
+ * @param array $config Auth-specific parameters
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', $port = null, $config = null)
+ {
+ if (is_array($config)) {
+ if (isset($config['username'])) {
+ $this->_username = $config['username'];
+ }
+ if (isset($config['password'])) {
+ $this->_password = $config['password'];
+ }
+ }
+
+ parent::__construct($host, $port, $config);
+ }
+
+
+ /**
+ * Perform LOGIN authentication with supplied credentials
+ *
+ * @return void
+ */
+ public function auth()
+ {
+ // Ensure AUTH has not already been initiated.
+ parent::auth();
+
+ $this->_send('AUTH LOGIN');
+ $this->_expect(334);
+ $this->_send(base64_encode($this->_username));
+ $this->_expect(334);
+ $this->_send(base64_encode($this->_password));
+ $this->_expect(235);
+ $this->_auth = true;
+ }
+}
diff --git a/lib/Zend/Mail/Protocol/Smtp/Auth/Plain.php b/lib/Zend/Mail/Protocol/Smtp/Auth/Plain.php
new file mode 100644
index 0000000..cb8d019
--- /dev/null
+++ b/lib/Zend/Mail/Protocol/Smtp/Auth/Plain.php
@@ -0,0 +1,96 @@
+<?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-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Plain.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mail_Protocol_Smtp
+ */
+require_once 'Zend/Mail/Protocol/Smtp.php';
+
+
+/**
+ * Performs PLAIN authentication
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Smtp_Auth_Plain extends Zend_Mail_Protocol_Smtp
+{
+ /**
+ * PLAIN username
+ *
+ * @var string
+ */
+ protected $_username;
+
+
+ /**
+ * PLAIN password
+ *
+ * @var string
+ */
+ protected $_password;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host (Default: 127.0.0.1)
+ * @param int $port (Default: null)
+ * @param array $config Auth-specific parameters
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', $port = null, $config = null)
+ {
+ if (is_array($config)) {
+ if (isset($config['username'])) {
+ $this->_username = $config['username'];
+ }
+ if (isset($config['password'])) {
+ $this->_password = $config['password'];
+ }
+ }
+
+ parent::__construct($host, $port, $config);
+ }
+
+
+ /**
+ * Perform PLAIN authentication with supplied credentials
+ *
+ * @return void
+ */
+ public function auth()
+ {
+ // Ensure AUTH has not already been initiated.
+ parent::auth();
+
+ $this->_send('AUTH PLAIN');
+ $this->_expect(334);
+ $this->_send(base64_encode(chr(0) . $this->_username . chr(0) . $this->_password));
+ $this->_expect(235);
+ $this->_auth = true;
+ }
+}