summaryrefslogtreecommitdiff
path: root/lib/Zend/Mail
diff options
context:
space:
mode:
authordavehauenstein <davehauenstein@d4e419ec-0920-11de-bbfd-a7c1bc4c261e>2009-04-15 22:06:42 +0000
committerdavehauenstein <davehauenstein@d4e419ec-0920-11de-bbfd-a7c1bc4c261e>2009-04-15 22:06:42 +0000
commitc53d8d026021f97075fb2f4940ba22793c38fb6e (patch)
treec5d7865bc0fd226ecc7525d7de8a1d0b0c5c89fd /lib/Zend/Mail
parenta414fcee8e7dc733443c6a971162d0dd9d0e723f (diff)
downloadreadability-simple-c53d8d026021f97075fb2f4940ba22793c38fb6e.tar.bz2
readability-simple-c53d8d026021f97075fb2f4940ba22793c38fb6e.zip
added toolbar; functionality includes refresh button to get back to original page, print article, email a link to the article with a personal note
git-svn-id: http://arc90labs-readability.googlecode.com/svn/trunk@31 d4e419ec-0920-11de-bbfd-a7c1bc4c261e
Diffstat (limited to 'lib/Zend/Mail')
-rw-r--r--lib/Zend/Mail/Exception.php37
-rw-r--r--lib/Zend/Mail/Message.php112
-rw-r--r--lib/Zend/Mail/Message/File.php96
-rw-r--r--lib/Zend/Mail/Message/Interface.php55
-rw-r--r--lib/Zend/Mail/Part.php489
-rw-r--r--lib/Zend/Mail/Part/File.php198
-rw-r--r--lib/Zend/Mail/Part/Interface.php136
-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
-rw-r--r--lib/Zend/Mail/Storage.php39
-rw-r--r--lib/Zend/Mail/Storage/Abstract.php366
-rw-r--r--lib/Zend/Mail/Storage/Exception.php39
-rw-r--r--lib/Zend/Mail/Storage/Folder.php236
-rw-r--r--lib/Zend/Mail/Storage/Folder/Interface.php60
-rw-r--r--lib/Zend/Mail/Storage/Folder/Maildir.php265
-rw-r--r--lib/Zend/Mail/Storage/Folder/Mbox.php264
-rw-r--r--lib/Zend/Mail/Storage/Imap.php644
-rw-r--r--lib/Zend/Mail/Storage/Maildir.php475
-rw-r--r--lib/Zend/Mail/Storage/Mbox.php447
-rw-r--r--lib/Zend/Mail/Storage/Pop3.php328
-rw-r--r--lib/Zend/Mail/Storage/Writable/Interface.php108
-rw-r--r--lib/Zend/Mail/Storage/Writable/Maildir.php1049
-rw-r--r--lib/Zend/Mail/Transport/Abstract.php350
-rw-r--r--lib/Zend/Mail/Transport/Exception.php39
-rw-r--r--lib/Zend/Mail/Transport/Sendmail.php170
-rw-r--r--lib/Zend/Mail/Transport/Smtp.php241
32 files changed, 8720 insertions, 0 deletions
diff --git a/lib/Zend/Mail/Exception.php b/lib/Zend/Mail/Exception.php
new file mode 100644
index 0000000..bab758e
--- /dev/null
+++ b/lib/Zend/Mail/Exception.php
@@ -0,0 +1,37 @@
+<?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
+ * @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/Exception.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @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_Exception extends Zend_Exception
+{}
+
diff --git a/lib/Zend/Mail/Message.php b/lib/Zend/Mail/Message.php
new file mode 100644
index 0000000..787df98
--- /dev/null
+++ b/lib/Zend/Mail/Message.php
@@ -0,0 +1,112 @@
+<?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
+ * @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: Message.php 9099 2008-03-30 19:35:47Z thomas $
+ */
+
+
+/**
+ * Zend_Mail_Part
+ */
+require_once 'Zend/Mail/Part.php';
+
+/**
+ * Zend_Mail_Message_Interface
+ */
+require_once 'Zend/Mail/Message/Interface.php';
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @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_Message extends Zend_Mail_Part implements Zend_Mail_Message_Interface
+{
+ /**
+ * flags for this message
+ * @var array
+ */
+ protected $_flags = array();
+
+ /**
+ * Public constructor
+ *
+ * In addition to the parameters of Zend_Mail_Part::__construct() this constructor supports:
+ * - file filename or file handle of a file with raw message content
+ * - flags array with flags for message, keys are ignored, use constants defined in Zend_Mail_Storage
+ *
+ * @param string $rawMessage full message with or without headers
+ * @throws Zend_Mail_Exception
+ */
+ public function __construct(array $params)
+ {
+ if (isset($params['file'])) {
+ if (!is_resource($params['file'])) {
+ $params['raw'] = @file_get_contents($params['file']);
+ if ($params['raw'] === false) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('could not open file');
+ }
+ } else {
+ $params['raw'] = stream_get_contents($params['file']);
+ }
+ }
+
+ if (!empty($params['flags'])) {
+ // set key and value to the same value for easy lookup
+ $this->_flags = array_combine($params['flags'], $params['flags']);
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * return toplines as found after headers
+ *
+ * @return string toplines
+ */
+ public function getTopLines()
+ {
+ return $this->_topLines;
+ }
+
+ /**
+ * check if flag is set
+ *
+ * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage
+ * @return bool true if set, otherwise false
+ */
+ public function hasFlag($flag)
+ {
+ return isset($this->_flags[$flag]);
+ }
+
+ /**
+ * get all set flags
+ *
+ * @return array array with flags, key and value are the same for easy lookup
+ */
+ public function getFlags()
+ {
+ return $this->_flags;
+ }
+}
diff --git a/lib/Zend/Mail/Message/File.php b/lib/Zend/Mail/Message/File.php
new file mode 100644
index 0000000..5a93449
--- /dev/null
+++ b/lib/Zend/Mail/Message/File.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
+ * @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: Message.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * Zend_Mail_Part
+ */
+require_once 'Zend/Mail/Part/File.php';
+
+/**
+ * Zend_Mail_Message_Interface
+ */
+require_once 'Zend/Mail/Message/Interface.php';
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @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_Message_File extends Zend_Mail_Part_File implements Zend_Mail_Message_Interface
+{
+ /**
+ * flags for this message
+ * @var array
+ */
+ protected $_flags = array();
+
+ /**
+ * Public constructor
+ *
+ * In addition to the parameters of Zend_Mail_Part::__construct() this constructor supports:
+ * - flags array with flags for message, keys are ignored, use constants defined in Zend_Mail_Storage
+ *
+ * @param string $rawMessage full message with or without headers
+ * @throws Zend_Mail_Exception
+ */
+ public function __construct(array $params)
+ {
+ if (!empty($params['flags'])) {
+ // set key and value to the same value for easy lookup
+ $this->_flags = array_combine($params['flags'], $params['flags']);
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * return toplines as found after headers
+ *
+ * @return string toplines
+ */
+ public function getTopLines()
+ {
+ return $this->_topLines;
+ }
+
+ /**
+ * check if flag is set
+ *
+ * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage
+ * @return bool true if set, otherwise false
+ */
+ public function hasFlag($flag)
+ {
+ return isset($this->_flags[$flag]);
+ }
+
+ /**
+ * get all set flags
+ *
+ * @return array array with flags, key and value are the same for easy lookup
+ */
+ public function getFlags()
+ {
+ return $this->_flags;
+ }
+}
diff --git a/lib/Zend/Mail/Message/Interface.php b/lib/Zend/Mail/Message/Interface.php
new file mode 100644
index 0000000..92acd49
--- /dev/null
+++ b/lib/Zend/Mail/Message/Interface.php
@@ -0,0 +1,55 @@
+<?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 Storage
+ * @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: Interface.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+interface Zend_Mail_Message_Interface
+{
+ /**
+ * return toplines as found after headers
+ *
+ * @return string toplines
+ */
+ public function getTopLines();
+
+ /**
+ * check if flag is set
+ *
+ * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage
+ * @return bool true if set, otherwise false
+ */
+ public function hasFlag($flag);
+
+ /**
+ * get all set flags
+ *
+ * @return array array with flags, key and value are the same for easy lookup
+ */
+ public function getFlags();
+} \ No newline at end of file
diff --git a/lib/Zend/Mail/Part.php b/lib/Zend/Mail/Part.php
new file mode 100644
index 0000000..c2f6bef
--- /dev/null
+++ b/lib/Zend/Mail/Part.php
@@ -0,0 +1,489 @@
+<?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
+ * @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: Part.php 14073 2009-02-12 22:30:40Z beberlei $
+ */
+
+
+/**
+ * @see Zend_Mime_Decode
+ */
+require_once 'Zend/Mime/Decode.php';
+
+/**
+ * @see Zend_Mail_Part_Interface
+ */
+require_once 'Zend/Mail/Part/Interface.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @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_Part implements RecursiveIterator, Zend_Mail_Part_Interface
+{
+ /**
+ * headers of part as array
+ * @var null|array
+ */
+ protected $_headers;
+
+ /**
+ * raw part body
+ * @var null|string
+ */
+ protected $_content;
+
+ /**
+ * toplines as fetched with headers
+ * @var string
+ */
+ protected $_topLines = '';
+
+ /**
+ * parts of multipart message
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * count of parts of a multipart message
+ * @var null|int
+ */
+ protected $_countParts;
+
+ /**
+ * current position of iterator
+ * @var int
+ */
+ protected $_iterationPos = 1;
+
+ /**
+ * mail handler, if late fetch is active
+ * @var null|Zend_Mail_Storage_Abstract
+ */
+ protected $_mail;
+
+ /**
+ * message number for mail handler
+ * @var int
+ */
+ protected $_messageNum = 0;
+
+ /**
+ * Public constructor
+ *
+ * Zend_Mail_Part supports different sources for content. The possible params are:
+ * - handler a instance of Zend_Mail_Storage_Abstract for late fetch
+ * - id number of message for handler
+ * - raw raw content with header and body as string
+ * - headers headers as array (name => value) or string, if a content part is found it's used as toplines
+ * - noToplines ignore content found after headers in param 'headers'
+ * - content content as string
+ *
+ * @param array $params full message with or without headers
+ * @throws Zend_Mail_Exception
+ */
+ public function __construct(array $params)
+ {
+ if (isset($params['handler'])) {
+ if (!$params['handler'] instanceof Zend_Mail_Storage_Abstract) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('handler is not a valid mail handler');
+ }
+ if (!isset($params['id'])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('need a message id with a handler');
+ }
+
+ $this->_mail = $params['handler'];
+ $this->_messageNum = $params['id'];
+ }
+
+ if (isset($params['raw'])) {
+ Zend_Mime_Decode::splitMessage($params['raw'], $this->_headers, $this->_content);
+ } else if (isset($params['headers'])) {
+ if (is_array($params['headers'])) {
+ $this->_headers = $params['headers'];
+ } else {
+ if (!empty($params['noToplines'])) {
+ Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $null);
+ } else {
+ Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $this->_topLines);
+ }
+ }
+ if (isset($params['content'])) {
+ $this->_content = $params['content'];
+ }
+ }
+ }
+
+ /**
+ * Check if part is a multipart message
+ *
+ * @return bool if part is multipart
+ */
+ public function isMultipart()
+ {
+ try {
+ return stripos($this->contentType, 'multipart/') === 0;
+ } catch(Zend_Mail_Exception $e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Body of part
+ *
+ * If part is multipart the raw content of this part with all sub parts is returned
+ *
+ * @return string body
+ * @throws Zend_Mail_Exception
+ */
+ public function getContent()
+ {
+ if ($this->_content !== null) {
+ return $this->_content;
+ }
+
+ if ($this->_mail) {
+ return $this->_mail->getRawContent($this->_messageNum);
+ } else {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('no content');
+ }
+ }
+
+ /**
+ * Return size of part
+ *
+ * Quite simple implemented currently (not decoding). Handle with care.
+ *
+ * @return int size
+ */
+ public function getSize() {
+ return strlen($this->getContent());
+ }
+
+
+ /**
+ * Cache content and split in parts if multipart
+ *
+ * @return null
+ * @throws Zend_Mail_Exception
+ */
+ protected function _cacheContent()
+ {
+ // caching content if we can't fetch parts
+ if ($this->_content === null && $this->_mail) {
+ $this->_content = $this->_mail->getRawContent($this->_messageNum);
+ }
+
+ if (!$this->isMultipart()) {
+ return;
+ }
+
+ // split content in parts
+ $boundary = $this->getHeaderField('content-type', 'boundary');
+ if (!$boundary) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('no boundary found in content type to split message');
+ }
+ $parts = Zend_Mime_Decode::splitMessageStruct($this->_content, $boundary);
+ if ($parts === null) {
+ return;
+ }
+ $counter = 1;
+ foreach ($parts as $part) {
+ $this->_parts[$counter++] = new self(array('headers' => $part['header'], 'content' => $part['body']));
+ }
+ }
+
+ /**
+ * Get part of multipart message
+ *
+ * @param int $num number of part starting with 1 for first part
+ * @return Zend_Mail_Part wanted part
+ * @throws Zend_Mail_Exception
+ */
+ public function getPart($num)
+ {
+ if (isset($this->_parts[$num])) {
+ return $this->_parts[$num];
+ }
+
+ if (!$this->_mail && $this->_content === null) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('part not found');
+ }
+
+ if ($this->_mail && $this->_mail->hasFetchPart) {
+ // TODO: fetch part
+ // return
+ }
+
+ $this->_cacheContent();
+
+ if (!isset($this->_parts[$num])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('part not found');
+ }
+
+ return $this->_parts[$num];
+ }
+
+ /**
+ * Count parts of a multipart part
+ *
+ * @return int number of sub-parts
+ */
+ public function countParts()
+ {
+ if ($this->_countParts) {
+ return $this->_countParts;
+ }
+
+ $this->_countParts = count($this->_parts);
+ if ($this->_countParts) {
+ return $this->_countParts;
+ }
+
+ if ($this->_mail && $this->_mail->hasFetchPart) {
+ // TODO: fetch part
+ // return
+ }
+
+ $this->_cacheContent();
+
+ $this->_countParts = count($this->_parts);
+ return $this->_countParts;
+ }
+
+
+ /**
+ * Get all headers
+ *
+ * The returned headers are as saved internally. All names are lowercased. The value is a string or an array
+ * if a header with the same name occurs more than once.
+ *
+ * @return array headers as array(name => value)
+ */
+ public function getHeaders()
+ {
+ if ($this->_headers === null) {
+ if (!$this->_mail) {
+ $this->_headers = array();
+ } else {
+ $part = $this->_mail->getRawHeader($this->_messageNum);
+ Zend_Mime_Decode::splitMessage($part, $this->_headers, $null);
+ }
+ }
+
+ return $this->_headers;
+ }
+
+ /**
+ * Get a header in specificed format
+ *
+ * Internally headers that occur more than once are saved as array, all other as string. If $format
+ * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim).
+ *
+ * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes
+ * @param string $format change type of return value to 'string' or 'array'
+ * @return string|array value of header in wanted or internal format
+ * @throws Zend_Mail_Exception
+ */
+ public function getHeader($name, $format = null)
+ {
+ if ($this->_headers === null) {
+ $this->getHeaders();
+ }
+
+ $lowerName = strtolower($name);
+
+ if (!isset($this->_headers[$lowerName])) {
+ $lowerName = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $name));
+ if (!isset($this->_headers[$lowerName])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception("no Header with Name $name found");
+ }
+ }
+ $name = $lowerName;
+
+ $header = $this->_headers[$name];
+
+ switch ($format) {
+ case 'string':
+ if (is_array($header)) {
+ $header = implode(Zend_Mime::LINEEND, $header);
+ }
+ break;
+ case 'array':
+ $header = (array)$header;
+ default:
+ // do nothing
+ }
+
+ return $header;
+ }
+
+ /**
+ * Get a specific field from a header like content type or all fields as array
+ *
+ * If the header occurs more than once, only the value from the first header
+ * is returned.
+ *
+ * Throws a Zend_Mail_Exception if the requested header does not exist. If
+ * the specific header field does not exist, returns null.
+ *
+ * @param string $name name of header, like in getHeader()
+ * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned
+ * @param string $firstName key name for the first part
+ * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
+ * @throws Zend_Exception, Zend_Mail_Exception
+ */
+ public function getHeaderField($name, $wantedPart = 0, $firstName = 0) {
+ return Zend_Mime_Decode::splitHeaderField(current($this->getHeader($name, 'array')), $wantedPart, $firstName);
+ }
+
+
+ /**
+ * Getter for mail headers - name is matched in lowercase
+ *
+ * This getter is short for Zend_Mail_Part::getHeader($name, 'string')
+ *
+ * @see Zend_Mail_Part::getHeader()
+ *
+ * @param string $name header name
+ * @return string value of header
+ * @throws Zend_Mail_Exception
+ */
+ public function __get($name)
+ {
+ return $this->getHeader($name, 'string');
+ }
+
+ /**
+ * magic method to get content of part
+ *
+ * @return string content
+ */
+ public function __toString()
+ {
+ return $this->getContent();
+ }
+
+ /**
+ * implements RecursiveIterator::hasChildren()
+ *
+ * @return bool current element has children/is multipart
+ */
+ public function hasChildren()
+ {
+ $current = $this->current();
+ return $current && $current instanceof Zend_Mail_Part && $current->isMultipart();
+ }
+
+ /**
+ * implements RecursiveIterator::getChildren()
+ *
+ * @return Zend_Mail_Part same as self::current()
+ */
+ public function getChildren()
+ {
+ return $this->current();
+ }
+
+ /**
+ * implements Iterator::valid()
+ *
+ * @return bool check if there's a current element
+ */
+ public function valid()
+ {
+ if ($this->_countParts === null) {
+ $this->countParts();
+ }
+ return $this->_iterationPos && $this->_iterationPos <= $this->_countParts;
+ }
+
+ /**
+ * implements Iterator::next()
+ *
+ * @return null
+ */
+ public function next()
+ {
+ ++$this->_iterationPos;
+ }
+
+ /**
+ * implements Iterator::key()
+ *
+ * @return string key/number of current part
+ */
+ public function key()
+ {
+ return $this->_iterationPos;
+ }
+
+ /**
+ * implements Iterator::current()
+ *
+ * @return Zend_Mail_Part current part
+ */
+ public function current()
+ {
+ return $this->getPart($this->_iterationPos);
+ }
+
+ /**
+ * implements Iterator::rewind()
+ *
+ * @return null
+ */
+ public function rewind()
+ {
+ $this->countParts();
+ $this->_iterationPos = 1;
+ }
+}
diff --git a/lib/Zend/Mail/Part/File.php b/lib/Zend/Mail/Part/File.php
new file mode 100644
index 0000000..6ff5147
--- /dev/null
+++ b/lib/Zend/Mail/Part/File.php
@@ -0,0 +1,198 @@
+<?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
+ * @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: Part.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mime_Decode
+ */
+require_once 'Zend/Mime/Decode.php';
+
+/**
+ * @see Zend_Mail_Part
+ */
+require_once 'Zend/Mail/Part.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @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_Part_File extends Zend_Mail_Part
+{
+ protected $_contentPos = array();
+ protected $_partPos = array();
+ protected $_fh;
+
+ /**
+ * Public constructor
+ *
+ * This handler supports the following params:
+ * - file filename or open file handler with message content (required)
+ * - startPos start position of message or part in file (default: current position)
+ * - endPos end position of message or part in file (default: end of file)
+ *
+ * @param array $params full message with or without headers
+ * @throws Zend_Mail_Exception
+ */
+ public function __construct(array $params)
+ {
+ if (empty($params['file'])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('no file given in params');
+ }
+
+ if (!is_resource($params['file'])) {
+ $this->_fh = fopen($params['file'], 'r');
+ } else {
+ $this->_fh = $params['file'];
+ }
+ if (!$this->_fh) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('could not open file');
+ }
+ if (isset($params['startPos'])) {
+ fseek($this->_fh, $params['startPos']);
+ }
+ $header = '';
+ $endPos = isset($params['endPos']) ? $params['endPos'] : null;
+ while (($endPos === null || ftell($this->_fh) < $endPos) && trim($line = fgets($this->_fh))) {
+ $header .= $line;
+ }
+
+ Zend_Mime_Decode::splitMessage($header, $this->_headers, $null);
+
+ $this->_contentPos[0] = ftell($this->_fh);
+ if ($endPos !== null) {
+ $this->_contentPos[1] = $endPos;
+ } else {
+ fseek($this->_fh, 0, SEEK_END);
+ $this->_contentPos[1] = ftell($this->_fh);
+ }
+ if (!$this->isMultipart()) {
+ return;
+ }
+
+ $boundary = $this->getHeaderField('content-type', 'boundary');
+ if (!$boundary) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('no boundary found in content type to split message');
+ }
+
+ $part = array();
+ $pos = $this->_contentPos[0];
+ fseek($this->_fh, $pos);
+ while (!feof($this->_fh) && ($endPos === null || $pos < $endPos)) {
+ $line = fgets($this->_fh);
+ if ($line === false) {
+ if (feof($this->_fh)) {
+ break;
+ }
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('error reading file');
+ }
+
+ $lastPos = $pos;
+ $pos = ftell($this->_fh);
+ $line = trim($line);
+
+ if ($line == '--' . $boundary) {
+ if ($part) {
+ // not first part
+ $part[1] = $lastPos;
+ $this->_partPos[] = $part;
+ }
+ $part = array($pos);
+ } else if ($line == '--' . $boundary . '--') {
+ $part[1] = $lastPos;
+ $this->_partPos[] = $part;
+ break;
+ }
+ }
+ $this->_countParts = count($this->_partPos);
+
+ }
+
+
+ /**
+ * Body of part
+ *
+ * If part is multipart the raw content of this part with all sub parts is returned
+ *
+ * @return string body
+ * @throws Zend_Mail_Exception
+ */
+ public function getContent($stream = null)
+ {
+ fseek($this->_fh, $this->_contentPos[0]);
+ if ($stream !== null) {
+ return stream_copy_to_stream($this->_fh, $stream, $this->_contentPos[1] - $this->_contentPos[0]);
+ }
+ $length = $this->_contentPos[1] - $this->_contentPos[0];
+ return $length < 1 ? '' : fread($this->_fh, $length);
+ }
+
+ /**
+ * Return size of part
+ *
+ * Quite simple implemented currently (not decoding). Handle with care.
+ *
+ * @return int size
+ */
+ public function getSize() {
+ return $this->_contentPos[1] - $this->_contentPos[0];
+ }
+
+ /**
+ * Get part of multipart message
+ *
+ * @param int $num number of part starting with 1 for first part
+ * @return Zend_Mail_Part wanted part
+ * @throws Zend_Mail_Exception
+ */
+ public function getPart($num)
+ {
+ --$num;
+ if (!isset($this->_partPos[$num])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ require_once 'Zend/Mail/Exception.php';
+ throw new Zend_Mail_Exception('part not found');
+ }
+
+ return new self(array('file' => $this->_fh, 'startPos' => $this->_partPos[$num][0],
+ 'endPos' => $this->_partPos[$num][1]));
+ }
+}
diff --git a/lib/Zend/Mail/Part/Interface.php b/lib/Zend/Mail/Part/Interface.php
new file mode 100644
index 0000000..d1a22f1
--- /dev/null
+++ b/lib/Zend/Mail/Part/Interface.php
@@ -0,0 +1,136 @@
+<?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 Storage
+ * @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: Interface.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+interface Zend_Mail_Part_Interface extends RecursiveIterator
+{
+ /**
+ * Check if part is a multipart message
+ *
+ * @return bool if part is multipart
+ */
+ public function isMultipart();
+
+
+ /**
+ * Body of part
+ *
+ * If part is multipart the raw content of this part with all sub parts is returned
+ *
+ * @return string body
+ * @throws Zend_Mail_Exception
+ */
+ public function getContent();
+
+ /**
+ * Return size of part
+ *
+ * @return int size
+ */
+ public function getSize();
+
+ /**
+ * Get part of multipart message
+ *
+ * @param int $num number of part starting with 1 for first part
+ * @return Zend_Mail_Part wanted part
+ * @throws Zend_Mail_Exception
+ */
+ public function getPart($num);
+
+ /**
+ * Count parts of a multipart part
+ *
+ * @return int number of sub-parts
+ */
+ public function countParts();
+
+
+ /**
+ * Get all headers
+ *
+ * The returned headers are as saved internally. All names are lowercased. The value is a string or an array
+ * if a header with the same name occurs more than once.
+ *
+ * @return array headers as array(name => value)
+ */
+ public function getHeaders();
+
+ /**
+ * Get a header in specificed format
+ *
+ * Internally headers that occur more than once are saved as array, all other as string. If $format
+ * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim).
+ *
+ * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes
+ * @param string $format change type of return value to 'string' or 'array'
+ * @return string|array value of header in wanted or internal format
+ * @throws Zend_Mail_Exception
+ */
+ public function getHeader($name, $format = null);
+
+ /**
+ * Get a specific field from a header like content type or all fields as array
+ *
+ * If the header occurs more than once, only the value from the first header
+ * is returned.
+ *
+ * Throws a Zend_Mail_Exception if the requested header does not exist. If
+ * the specific header field does not exist, returns null.
+ *
+ * @param string $name name of header, like in getHeader()
+ * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned
+ * @param string $firstName key name for the first part
+ * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
+ * @throws Zend_Exception, Zend_Mail_Exception
+ */
+ public function getHeaderField($name, $wantedPart = 0, $firstName = 0);
+
+
+ /**
+ * Getter for mail headers - name is matched in lowercase
+ *
+ * This getter is short for Zend_Mail_Part::getHeader($name, 'string')
+ *
+ * @see Zend_Mail_Part::getHeader()
+ *
+ * @param string $name header name
+ * @return string value of header
+ * @throws Zend_Mail_Exception
+ */
+ public function __get($name);
+
+ /**
+ * magic method to get content of part
+ *
+ * @return string content
+ */
+ public function __toString();
+} \ No newline at end of file
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;
+ }
+}
diff --git a/lib/Zend/Mail/Storage.php b/lib/Zend/Mail/Storage.php
new file mode 100644
index 0000000..227f496
--- /dev/null
+++ b/lib/Zend/Mail/Storage.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
+ * @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: Storage.php 9099 2008-03-30 19:35:47Z thomas $
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @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_Storage
+{
+ // maildir and IMAP flags, using IMAP names, where possible to be able to distinguish between IMAP
+ // system flags and other flags
+ const FLAG_PASSED = 'Passed';
+ const FLAG_SEEN = '\Seen';
+ const FLAG_ANSWERED = '\Answered';
+ const FLAG_FLAGGED = '\Flagged';
+ const FLAG_DELETED = '\Deleted';
+ const FLAG_DRAFT = '\Draft';
+ const FLAG_RECENT = '\Recent';
+}
diff --git a/lib/Zend/Mail/Storage/Abstract.php b/lib/Zend/Mail/Storage/Abstract.php
new file mode 100644
index 0000000..9e84d93
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Abstract.php
@@ -0,0 +1,366 @@
+<?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 Storage
+ * @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 9099 2008-03-30 19:35:47Z thomas $
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Mail_Storage_Abstract implements Countable, ArrayAccess, SeekableIterator
+{
+ /**
+ * class capabilities with default values
+ * @var array
+ */
+ protected $_has = array('uniqueid' => true,
+ 'delete' => false,
+ 'create' => false,
+ 'top' => false,
+ 'fetchPart' => true,
+ 'flags' => false);
+
+ /**
+ * current iteration position
+ * @var int
+ */
+ protected $_iterationPos = 0;
+
+ /**
+ * maximum iteration position (= message count)
+ * @var null|int
+ */
+ protected $_iterationMax = null;
+
+ /**
+ * used message class, change it in an extened class to extend the returned message class
+ * @var string
+ */
+ protected $_messageClass = 'Zend_Mail_Message';
+
+ /**
+ * Getter for has-properties. The standard has properties
+ * are: hasFolder, hasUniqueid, hasDelete, hasCreate, hasTop
+ *
+ * The valid values for the has-properties are:
+ * - true if a feature is supported
+ * - false if a feature is not supported
+ * - null is it's not yet known or it can't be know if a feature is supported
+ *
+ * @param string $var property name
+ * @return bool supported or not
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __get($var)
+ {
+ if (strpos($var, 'has') === 0) {
+ $var = strtolower(substr($var, 3));
+ return isset($this->_has[$var]) ? $this->_has[$var] : null;
+ }
+
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception($var . ' not found');
+ }
+
+
+ /**
+ * Get a full list of features supported by the specific mail lib and the server
+ *
+ * @return array list of features as array(featurename => true|false[|null])
+ */
+ public function getCapabilities()
+ {
+ return $this->_has;
+ }
+
+
+ /**
+ * Count messages messages in current box/folder
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ */
+ abstract public function countMessages();
+
+
+ /**
+ * Get a list of messages with number and size
+ *
+ * @param int $id number of message
+ * @return int|array size of given message of list with all messages as array(num => size)
+ */
+ abstract public function getSize($id = 0);
+
+
+ /**
+ * Get a message with headers and body
+ *
+ * @param $id int number of message
+ * @return Zend_Mail_Message
+ */
+ abstract public function getMessage($id);
+
+
+ /**
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ */
+ abstract public function getRawHeader($id, $part = null, $topLines = 0);
+
+ /**
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ */
+ abstract public function getRawContent($id, $part = null);
+
+ /**
+ * Create instance with parameters
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ abstract public function __construct($params);
+
+
+ /**
+ * Destructor calls close() and therefore closes the resource.
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return null
+ */
+ abstract public function close();
+
+
+ /**
+ * Keep the resource alive.
+ *
+ * @return null
+ */
+ abstract public function noop();
+
+ /**
+ * delete a message from current box/folder
+ *
+ * @return null
+ */
+ abstract public function removeMessage($id);
+
+ /**
+ * get unique id for one or all messages
+ *
+ * if storage does not support unique ids it's the same as the message number
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ abstract public function getUniqueId($id = null);
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ abstract public function getNumberByUniqueId($id);
+
+ // interface implementations follows
+
+ /**
+ * Countable::count()
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->countMessages();
+ }
+
+
+ /**
+ * ArrayAccess::offsetExists()
+ *
+ * @param int $id
+ * @return boolean
+ */
+ public function offsetExists($id)
+ {
+ try {
+ if ($this->getMessage($id)) {
+ return true;
+ }
+ } catch(Zend_Mail_Storage_Exception $e) {}
+
+ return false;
+ }
+
+
+ /**
+ * ArrayAccess::offsetGet()
+ *
+ * @param int $id
+ * @return Zend_Mail_Message message object
+ */
+ public function offsetGet($id)
+ {
+ return $this->getMessage($id);
+ }
+
+
+ /**
+ * ArrayAccess::offsetSet()
+ *
+ * @param id $id
+ * @param mixed $value
+ * @throws Zend_Mail_Storage_Exception
+ * @return void
+ */
+ public function offsetSet($id, $value)
+ {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot write mail messages via array access');
+ }
+
+
+ /**
+ * ArrayAccess::offsetUnset()
+ *
+ * @param int $id
+ * @return boolean success
+ */
+ public function offsetUnset($id)
+ {
+ return $this->removeMessage($id);
+ }
+
+
+ /**
+ * Iterator::rewind()
+ *
+ * Rewind always gets the new count from the storage. Thus if you use
+ * the interfaces and your scripts take long you should use reset()
+ * from time to time.
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ $this->_iterationMax = $this->countMessages();
+ $this->_iterationPos = 1;
+ }
+
+
+ /**
+ * Iterator::current()
+ *
+ * @return Zend_Mail_Message current message
+ */
+ public function current()
+ {
+ return $this->getMessage($this->_iterationPos);
+ }
+
+
+ /**
+ * Iterator::key()
+ *
+ * @return int id of current position
+ */
+ public function key()
+ {
+ return $this->_iterationPos;
+ }
+
+
+ /**
+ * Iterator::next()
+ *
+ * @return void
+ */
+ public function next()
+ {
+ ++$this->_iterationPos;
+ }
+
+
+ /**
+ * Iterator::valid()
+ *
+ * @return boolean
+ */
+ public function valid()
+ {
+ if ($this->_iterationMax === null) {
+ $this->_iterationMax = $this->countMessages();
+ }
+ return $this->_iterationPos && $this->_iterationPos <= $this->_iterationMax;
+ }
+
+
+ /**
+ * SeekableIterator::seek()
+ *
+ * @param int $pos
+ * @return void
+ * @throws OutOfBoundsException
+ */
+ public function seek($pos)
+ {
+ if ($this->_iterationMax === null) {
+ $this->_iterationMax = $this->countMessages();
+ }
+
+ if ($pos > $this->_iterationMax) {
+ throw new OutOfBoundsException('this position does not exist');
+ }
+ $this->_iterationPos = $pos;
+ }
+
+}
diff --git a/lib/Zend/Mail/Storage/Exception.php b/lib/Zend/Mail/Storage/Exception.php
new file mode 100644
index 0000000..1aea4f9
--- /dev/null
+++ b/lib/Zend/Mail/Storage/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 Storage
+ * @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_Mail_Exception
+ */
+require_once 'Zend/Mail/Exception.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Exception extends Zend_Mail_Exception
+{}
+
diff --git a/lib/Zend/Mail/Storage/Folder.php b/lib/Zend/Mail/Storage/Folder.php
new file mode 100644
index 0000000..6ee9abe
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Folder.php
@@ -0,0 +1,236 @@
+<?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 Storage
+ * @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: Folder.php 9099 2008-03-30 19:35:47Z thomas $
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Folder implements RecursiveIterator
+{
+ /**
+ * subfolders of folder array(localName => Zend_Mail_Storage_Folder folder)
+ * @var array
+ */
+ protected $_folders;
+
+ /**
+ * local name (name of folder in parent folder)
+ * @var string
+ */
+ protected $_localName;
+
+ /**
+ * global name (absolute name of folder)
+ * @var string
+ */
+ protected $_globalName;
+
+ /**
+ * folder is selectable if folder is able to hold messages, else it's just a parent folder
+ * @var bool
+ */
+ protected $_selectable = true;
+
+ /**
+ * create a new mail folder instance
+ *
+ * @param string $localName name of folder in current subdirectory
+ * @param string $globalName absolute name of folder
+ * @param bool $selectable if true folder holds messages, if false it's just a parent for subfolders
+ * @param array $folders init with given instances of Zend_Mail_Storage_Folder as subfolders
+ */
+ public function __construct($localName, $globalName = '', $selectable = true, array $folders = array())
+ {
+ $this->_localName = $localName;
+ $this->_globalName = $globalName ? $globalName : $localName;
+ $this->_selectable = $selectable;
+ $this->_folders = $folders;
+ }
+
+ /**
+ * implements RecursiveIterator::hasChildren()
+ *
+ * @return bool current element has children
+ */
+ public function hasChildren()
+ {
+ $current = $this->current();
+ return $current && $current instanceof Zend_Mail_Storage_Folder && !$current->isLeaf();
+ }
+
+ /**
+ * implements RecursiveIterator::getChildren()
+ *
+ * @return Zend_Mail_Storage_Folder same as self::current()
+ */
+ public function getChildren()
+ {
+ return $this->current();
+ }
+
+ /**
+ * implements Iterator::valid()
+ *
+ * @return bool check if there's a current element
+ */
+ public function valid()
+ {
+ return key($this->_folders) !== null;
+ }
+
+ /**
+ * implements Iterator::next()
+ *
+ * @return null
+ */
+ public function next()
+ {
+ next($this->_folders);
+ }
+
+ /**
+ * implements Iterator::key()
+ *
+ * @return string key/local name of current element
+ */
+ public function key()
+ {
+ return key($this->_folders);
+ }
+
+ /**
+ * implements Iterator::current()
+ *
+ * @return Zend_Mail_Storage_Folder current folder
+ */
+ public function current()
+ {
+ return current($this->_folders);
+ }
+
+ /**
+ * implements Iterator::rewind()
+ *
+ * @return null
+ */
+ public function rewind()
+ {
+ reset($this->_folders);
+ }
+
+ /**
+ * get subfolder named $name
+ *
+ * @param string $name wanted subfolder
+ * @return Zend_Mail_Storage_Folder folder named $folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __get($name)
+ {
+ if (!isset($this->_folders[$name])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("no subfolder named $name");
+ }
+
+ return $this->_folders[$name];
+ }
+
+ /**
+ * add or replace subfolder named $name
+ *
+ * @param string $name local name of subfolder
+ * @param Zend_Mail_Storage_Folder $folder instance for new subfolder
+ * @return null
+ */
+ public function __set($name, Zend_Mail_Storage_Folder $folder)
+ {
+ $this->_folders[$name] = $folder;
+ }
+
+ /**
+ * remove subfolder named $name
+ *
+ * @param string $name local name of subfolder
+ * @return null
+ */
+ public function __unset($name)
+ {
+ unset($this->_folders[$name]);
+ }
+
+ /**
+ * magic method for easy output of global name
+ *
+ * @return string global name of folder
+ */
+ public function __toString()
+ {
+ return (string)$this->getGlobalName();
+ }
+
+ /**
+ * get local name
+ *
+ * @return string local name
+ */
+ public function getLocalName()
+ {
+ return $this->_localName;
+ }
+
+ /**
+ * get global name
+ *
+ * @return string global name
+ */
+ public function getGlobalName()
+ {
+ return $this->_globalName;
+ }
+
+ /**
+ * is this folder selectable?
+ *
+ * @return bool selectable
+ */
+ public function isSelectable()
+ {
+ return $this->_selectable;
+ }
+
+ /**
+ * check if folder has no subfolder
+ *
+ * @return bool true if no subfolders
+ */
+ public function isLeaf()
+ {
+ return empty($this->_folders);
+ }
+}
diff --git a/lib/Zend/Mail/Storage/Folder/Interface.php b/lib/Zend/Mail/Storage/Folder/Interface.php
new file mode 100644
index 0000000..cb148b7
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Folder/Interface.php
@@ -0,0 +1,60 @@
+<?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 Storage
+ * @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: Interface.php 9098 2008-03-30 19:29:10Z thomas $
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Mail_Storage_Folder_Interface
+{
+ /**
+ * get root folder or given folder
+ *
+ * @param string $rootFolder get folder structure for given folder, else root
+ * @return Zend_Mail_Storage_Folder root or wanted folder
+ */
+ public function getFolders($rootFolder = null);
+
+ /**
+ * select given folder
+ *
+ * folder must be selectable!
+ *
+ * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function selectFolder($globalName);
+
+
+ /**
+ * get Zend_Mail_Storage_Folder instance for current folder
+ *
+ * @return Zend_Mail_Storage_Folder instance of current folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getCurrentFolder();
+}
diff --git a/lib/Zend/Mail/Storage/Folder/Maildir.php b/lib/Zend/Mail/Storage/Folder/Maildir.php
new file mode 100644
index 0000000..30b31aa
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Folder/Maildir.php
@@ -0,0 +1,265 @@
+<?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 Storage
+ * @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: Maildir.php 9098 2008-03-30 19:29:10Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Folder
+ */
+require_once 'Zend/Mail/Storage/Folder.php';
+
+/**
+ * @see Zend_Mail_Storage_Folder_Interface
+ */
+require_once 'Zend/Mail/Storage/Folder/Interface.php';
+
+/**
+ * @see Zend_Mail_Storage_Maildir
+ */
+require_once 'Zend/Mail/Storage/Maildir.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Folder_Maildir extends Zend_Mail_Storage_Maildir implements Zend_Mail_Storage_Folder_Interface
+{
+ /**
+ * Zend_Mail_Storage_Folder root folder for folder structure
+ * @var Zend_Mail_Storage_Folder
+ */
+ protected $_rootFolder;
+
+ /**
+ * rootdir of folder structure
+ * @var string
+ */
+ protected $_rootdir;
+
+ /**
+ * name of current folder
+ * @var string
+ */
+ protected $_currentFolder;
+
+ /**
+ * delim char for subfolders
+ * @var string
+ */
+ protected $_delim;
+
+ /**
+ * Create instance with parameters
+ * Supported parameters are:
+ * - dirname rootdir of maildir structure
+ * - delim delim char for folder structur, default is '.'
+ * - folder intial selected folder, default is 'INBOX'
+ *
+ * @param $params array mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (!isset($params->dirname) || !is_dir($params->dirname)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
+ }
+
+ $this->_rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+
+ $this->_delim = isset($params->delim) ? $params->delim : '.';
+
+ $this->_buildFolderTree();
+ $this->selectFolder(!empty($params->folder) ? $params->folder : 'INBOX');
+ $this->_has['top'] = true;
+ $this->_has['flags'] = true;
+ }
+
+ /**
+ * find all subfolders and mbox files for folder structure
+ *
+ * Result is save in Zend_Mail_Storage_Folder instances with the root in $this->_rootFolder.
+ * $parentFolder and $parentGlobalName are only used internally for recursion.
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _buildFolderTree()
+ {
+ $this->_rootFolder = new Zend_Mail_Storage_Folder('/', '/', false);
+ $this->_rootFolder->INBOX = new Zend_Mail_Storage_Folder('INBOX', 'INBOX', true);
+
+ $dh = @opendir($this->_rootdir);
+ if (!$dh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("can't read folders in maildir");
+ }
+ $dirs = array();
+ while (($entry = readdir($dh)) !== false) {
+ // maildir++ defines folders must start with .
+ if ($entry[0] != '.' || $entry == '.' || $entry == '..') {
+ continue;
+ }
+ if ($this->_isMaildir($this->_rootdir . $entry)) {
+ $dirs[] = $entry;
+ }
+ }
+ closedir($dh);
+
+ sort($dirs);
+ $stack = array(null);
+ $folderStack = array(null);
+ $parentFolder = $this->_rootFolder;
+ $parent = '.';
+
+ foreach ($dirs as $dir) {
+ do {
+ if (strpos($dir, $parent) === 0) {
+ $local = substr($dir, strlen($parent));
+ if (strpos($local, $this->_delim) !== false) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('error while reading maildir');
+ }
+ array_push($stack, $parent);
+ $parent = $dir . $this->_delim;
+ $folder = new Zend_Mail_Storage_Folder($local, substr($dir, 1), true);
+ $parentFolder->$local = $folder;
+ array_push($folderStack, $parentFolder);
+ $parentFolder = $folder;
+ break;
+ } else if ($stack) {
+ $parent = array_pop($stack);
+ $parentFolder = array_pop($folderStack);
+ }
+ } while ($stack);
+ if (!$stack) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('error while reading maildir');
+ }
+ }
+ }
+
+ /**
+ * get root folder or given folder
+ *
+ * @param string $rootFolder get folder structure for given folder, else root
+ * @return Zend_Mail_Storage_Folder root or wanted folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getFolders($rootFolder = null)
+ {
+ if (!$rootFolder || $rootFolder == 'INBOX') {
+ return $this->_rootFolder;
+ }
+
+ // rootdir is same as INBOX in maildir
+ if (strpos($rootFolder, 'INBOX' . $this->_delim) === 0) {
+ $rootFolder = substr($rootFolder, 6);
+ }
+ $currentFolder = $this->_rootFolder;
+ $subname = trim($rootFolder, $this->_delim);
+ while ($currentFolder) {
+ @list($entry, $subname) = @explode($this->_delim, $subname, 2);
+ $currentFolder = $currentFolder->$entry;
+ if (!$subname) {
+ break;
+ }
+ }
+
+ if ($currentFolder->getGlobalName() != rtrim($rootFolder, $this->_delim)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("folder $rootFolder not found");
+ }
+ return $currentFolder;
+ }
+
+ /**
+ * select given folder
+ *
+ * folder must be selectable!
+ *
+ * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function selectFolder($globalName)
+ {
+ $this->_currentFolder = (string)$globalName;
+
+ // getting folder from folder tree for validation
+ $folder = $this->getFolders($this->_currentFolder);
+
+ try {
+ $this->_openMaildir($this->_rootdir . '.' . $folder->getGlobalName());
+ } catch(Zend_Mail_Storage_Exception $e) {
+ // check what went wrong
+ if (!$folder->isSelectable()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("{$this->_currentFolder} is not selectable");
+ }
+ // seems like file has vanished; rebuilding folder tree - but it's still an exception
+ $this->_buildFolderTree($this->_rootdir);
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('seems like the maildir has vanished, I\'ve rebuild the ' .
+ 'folder tree, search for an other folder and try again');
+ }
+ }
+
+ /**
+ * get Zend_Mail_Storage_Folder instance for current folder
+ *
+ * @return Zend_Mail_Storage_Folder instance of current folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getCurrentFolder()
+ {
+ return $this->_currentFolder;
+ }
+}
diff --git a/lib/Zend/Mail/Storage/Folder/Mbox.php b/lib/Zend/Mail/Storage/Folder/Mbox.php
new file mode 100644
index 0000000..797148d
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Folder/Mbox.php
@@ -0,0 +1,264 @@
+<?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 Storage
+ * @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: Mbox.php 9098 2008-03-30 19:29:10Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Folder
+ */
+require_once 'Zend/Mail/Storage/Folder.php';
+
+/**
+ * @see Zend_Mail_Storage_Folder_Interface
+ */
+require_once 'Zend/Mail/Storage/Folder/Interface.php';
+
+/**
+ * @see Zend_Mail_Storage_Mbox
+ */
+require_once 'Zend/Mail/Storage/Mbox.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Folder_Mbox extends Zend_Mail_Storage_Mbox implements Zend_Mail_Storage_Folder_Interface
+{
+ /**
+ * Zend_Mail_Storage_Folder root folder for folder structure
+ * @var Zend_Mail_Storage_Folder
+ */
+ protected $_rootFolder;
+
+ /**
+ * rootdir of folder structure
+ * @var string
+ */
+ protected $_rootdir;
+
+ /**
+ * name of current folder
+ * @var string
+ */
+ protected $_currentFolder;
+
+ /**
+ * Create instance with parameters
+ *
+ * Disallowed parameters are:
+ * - filename use Zend_Mail_Storage_Mbox for a single file
+ * Supported parameters are:
+ * - dirname rootdir of mbox structure
+ * - folder intial selected folder, default is 'INBOX'
+ *
+ * @param $params array mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (isset($params->filename)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('use Zend_Mail_Storage_Mbox for a single file');
+ }
+
+ if (!isset($params->dirname) || !is_dir($params->dirname)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
+ }
+
+ $this->_rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+
+ $this->_buildFolderTree($this->_rootdir);
+ $this->selectFolder(!empty($params->folder) ? $params->folder : 'INBOX');
+ $this->_has['top'] = true;
+ $this->_has['uniqueid'] = false;
+ }
+
+ /**
+ * find all subfolders and mbox files for folder structure
+ *
+ * Result is save in Zend_Mail_Storage_Folder instances with the root in $this->_rootFolder.
+ * $parentFolder and $parentGlobalName are only used internally for recursion.
+ *
+ * @param string $currentDir call with root dir, also used for recursion.
+ * @param Zend_Mail_Storage_Folder|null $parentFolder used for recursion
+ * @param string $parentGlobalName used for rescursion
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _buildFolderTree($currentDir, $parentFolder = null, $parentGlobalName = '')
+ {
+ if (!$parentFolder) {
+ $this->_rootFolder = new Zend_Mail_Storage_Folder('/', '/', false);
+ $parentFolder = $this->_rootFolder;
+ }
+
+ $dh = @opendir($currentDir);
+ if (!$dh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("can't read dir $currentDir");
+ }
+ while (($entry = readdir($dh)) !== false) {
+ // ignore hidden files for mbox
+ if ($entry[0] == '.') {
+ continue;
+ }
+ $absoluteEntry = $currentDir . $entry;
+ $globalName = $parentGlobalName . DIRECTORY_SEPARATOR . $entry;
+ if (is_file($absoluteEntry) && $this->_isMboxFile($absoluteEntry)) {
+ $parentFolder->$entry = new Zend_Mail_Storage_Folder($entry, $globalName);
+ continue;
+ }
+ if (!is_dir($absoluteEntry) /* || $entry == '.' || $entry == '..' */) {
+ continue;
+ }
+ $folder = new Zend_Mail_Storage_Folder($entry, $globalName, false);
+ $parentFolder->$entry = $folder;
+ $this->_buildFolderTree($absoluteEntry . DIRECTORY_SEPARATOR, $folder, $globalName);
+ }
+
+ closedir($dh);
+ }
+
+ /**
+ * get root folder or given folder
+ *
+ * @param string $rootFolder get folder structure for given folder, else root
+ * @return Zend_Mail_Storage_Folder root or wanted folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getFolders($rootFolder = null)
+ {
+ if (!$rootFolder) {
+ return $this->_rootFolder;
+ }
+
+ $currentFolder = $this->_rootFolder;
+ $subname = trim($rootFolder, DIRECTORY_SEPARATOR);
+ while ($currentFolder) {
+ @list($entry, $subname) = @explode(DIRECTORY_SEPARATOR, $subname, 2);
+ $currentFolder = $currentFolder->$entry;
+ if (!$subname) {
+ break;
+ }
+ }
+
+ if ($currentFolder->getGlobalName() != DIRECTORY_SEPARATOR . trim($rootFolder, DIRECTORY_SEPARATOR)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("folder $rootFolder not found");
+ }
+ return $currentFolder;
+ }
+
+ /**
+ * select given folder
+ *
+ * folder must be selectable!
+ *
+ * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function selectFolder($globalName)
+ {
+ $this->_currentFolder = (string)$globalName;
+
+ // getting folder from folder tree for validation
+ $folder = $this->getFolders($this->_currentFolder);
+
+ try {
+ $this->_openMboxFile($this->_rootdir . $folder->getGlobalName());
+ } catch(Zend_Mail_Storage_Exception $e) {
+ // check what went wrong
+ if (!$folder->isSelectable()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("{$this->_currentFolder} is not selectable");
+ }
+ // seems like file has vanished; rebuilding folder tree - but it's still an exception
+ $this->_buildFolderTree($this->_rootdir);
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('seems like the mbox file has vanished, I\'ve rebuild the ' .
+ 'folder tree, search for an other folder and try again');
+ }
+ }
+
+ /**
+ * get Zend_Mail_Storage_Folder instance for current folder
+ *
+ * @return Zend_Mail_Storage_Folder instance of current folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getCurrentFolder()
+ {
+ return $this->_currentFolder;
+ }
+
+ /**
+ * magic method for serialize()
+ *
+ * with this method you can cache the mbox class
+ *
+ * @return array name of variables
+ */
+ public function __sleep()
+ {
+ return array_merge(parent::__sleep(), array('_currentFolder', '_rootFolder', '_rootdir'));
+ }
+
+ /**
+ * magic method for unserialize()
+ *
+ * with this method you can cache the mbox class
+ *
+ * @return null
+ */
+ public function __wakeup()
+ {
+ // if cache is stall selectFolder() rebuilds the tree on error
+ parent::__wakeup();
+ }
+}
diff --git a/lib/Zend/Mail/Storage/Imap.php b/lib/Zend/Mail/Storage/Imap.php
new file mode 100644
index 0000000..f4051f9
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Imap.php
@@ -0,0 +1,644 @@
+<?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 Storage
+ * @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 12519 2008-11-10 18:41:24Z alexander $
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Abstract
+ */
+require_once 'Zend/Mail/Storage/Abstract.php';
+
+/**
+ * @see Zend_Mail_Protocol_Imap
+ */
+require_once 'Zend/Mail/Protocol/Imap.php';
+
+/**
+ * @see Zend_Mail_Storage_Writable_Interface
+ */
+require_once 'Zend/Mail/Storage/Writable/Interface.php';
+
+/**
+ * @see Zend_Mail_Storage_Folder_Interface
+ */
+require_once 'Zend/Mail/Storage/Folder/Interface.php';
+
+/**
+ * @see Zend_Mail_Storage_Folder
+ */
+require_once 'Zend/Mail/Storage/Folder.php';
+
+/**
+ * @see Zend_Mail_Message
+ */
+require_once 'Zend/Mail/Message.php';
+
+/**
+ * @see Zend_Mail_Storage
+ */
+require_once 'Zend/Mail/Storage.php';
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Imap extends Zend_Mail_Storage_Abstract
+ implements Zend_Mail_Storage_Folder_Interface, Zend_Mail_Storage_Writable_Interface
+{
+ // TODO: with an internal cache we could optimize this class, or create an extra class with
+ // such optimizations. Especially the various fetch calls could be combined to one cache call
+
+ /**
+ * protocol handler
+ * @var null|Zend_Mail_Protocol_Imap
+ */
+ protected $_protocol;
+
+ /**
+ * name of current folder
+ * @var string
+ */
+ protected $_currentFolder = '';
+
+ /**
+ * imap flags to constants translation
+ * @var array
+ */
+ protected static $_knownFlags = array('\Passed' => Zend_Mail_Storage::FLAG_PASSED,
+ '\Answered' => Zend_Mail_Storage::FLAG_ANSWERED,
+ '\Seen' => Zend_Mail_Storage::FLAG_SEEN,
+ '\Deleted' => Zend_Mail_Storage::FLAG_DELETED,
+ '\Draft' => Zend_Mail_Storage::FLAG_DRAFT,
+ '\Flagged' => Zend_Mail_Storage::FLAG_FLAGGED);
+
+ /**
+ * map flags to search criterias
+ * @var array
+ */
+ protected static $_searchFlags = array('\Recent' => 'RECENT',
+ '\Answered' => 'ANSWERED',
+ '\Seen' => 'SEEN',
+ '\Deleted' => 'DELETED',
+ '\Draft' => 'DRAFT',
+ '\Flagged' => 'FLAGGED');
+
+ /**
+ * Count messages all messages in current box
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function countMessages($flags = null)
+ {
+ if (!$this->_currentFolder) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('No selected folder to count');
+ }
+
+ if ($flags === null) {
+ return count($this->_protocol->search(array('ALL')));
+ }
+
+ $params = array();
+ foreach ((array)$flags as $flag) {
+ if (isset(self::$_searchFlags[$flag])) {
+ $params[] = self::$_searchFlags[$flag];
+ } else {
+ $params[] = 'KEYWORD';
+ $params[] = $this->_protocol->escapeString($flag);
+ }
+ }
+ return count($this->_protocol->search($params));
+ }
+
+ /**
+ * get a list of messages with number and size
+ *
+ * @param int $id number of message
+ * @return int|array size of given message of list with all messages as array(num => size)
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getSize($id = 0)
+ {
+ if ($id) {
+ return $this->_protocol->fetch('RFC822.SIZE', $id);
+ }
+ return $this->_protocol->fetch('RFC822.SIZE', 1, INF);
+ }
+
+ /**
+ * Fetch a message
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getMessage($id)
+ {
+ $data = $this->_protocol->fetch(array('FLAGS', 'RFC822.HEADER'), $id);
+ $header = $data['RFC822.HEADER'];
+
+ $flags = array();
+ foreach ($data['FLAGS'] as $flag) {
+ $flags[] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag;
+ }
+
+ return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $header, 'flags' => $flags));
+ }
+
+ /*
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawHeader($id, $part = null, $topLines = 0)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ // TODO: toplines
+ return $this->_protocol->fetch('RFC822.HEADER', $id);
+ }
+
+ /*
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawContent($id, $part = null)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ return $this->_protocol->fetch('RFC822.TEXT', $id);
+ }
+
+ /**
+ * create instance with parameters
+ * Supported paramters are
+ * - user username
+ * - host hostname or ip address of IMAP server [optional, default = 'localhost']
+ * - password password for user 'username' [optional, default = '']
+ * - port port for IMAP server [optional, default = 110]
+ * - ssl 'SSL' or 'TLS' for secure sockets
+ * - folder select this folder [optional, default = 'INBOX']
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ $this->_has['flags'] = true;
+
+ if ($params instanceof Zend_Mail_Protocol_Imap) {
+ $this->_protocol = $params;
+ try {
+ $this->selectFolder('INBOX');
+ } catch(Zend_Mail_Storage_Exception $e) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot select INBOX, is this a valid transport?');
+ }
+ return;
+ }
+
+ if (!isset($params->user)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('need at least user in params');
+ }
+
+ $host = isset($params->host) ? $params->host : 'localhost';
+ $password = isset($params->password) ? $params->password : '';
+ $port = isset($params->port) ? $params->port : null;
+ $ssl = isset($params->ssl) ? $params->ssl : false;
+
+ $this->_protocol = new Zend_Mail_Protocol_Imap();
+ $this->_protocol->connect($host, $port, $ssl);
+ if (!$this->_protocol->login($params->user, $password)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot login, user or password wrong');
+ }
+ $this->selectFolder(isset($params->folder) ? $params->folder : 'INBOX');
+ }
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return null
+ */
+ public function close()
+ {
+ $this->_currentFolder = '';
+ $this->_protocol->logout();
+ }
+
+ /**
+ * Keep the server busy.
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function noop()
+ {
+ if (!$this->_protocol->noop()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('could not do nothing');
+ }
+ }
+
+ /**
+ * Remove a message from server. If you're doing that from a web enviroment
+ * you should be careful and use a uniqueid as parameter if possible to
+ * identify the message.
+ *
+ * @param int $id number of message
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeMessage($id)
+ {
+ if (!$this->_protocol->store(array(Zend_Mail_Storage::FLAG_DELETED), $id, null, '+')) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot set deleted flag');
+ }
+ // TODO: expunge here or at close? we can handle an error here better and are more fail safe
+ if (!$this->_protocol->expunge()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('message marked as deleted, but could not expunge');
+ }
+ }
+
+ /**
+ * get unique id for one or all messages
+ *
+ * if storage does not support unique ids it's the same as the message number
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getUniqueId($id = null)
+ {
+ if ($id) {
+ return $this->_protocol->fetch('UID', $id);
+ }
+
+ return $this->_protocol->fetch('UID', 1, INF);
+ }
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getNumberByUniqueId($id)
+ {
+ // TODO: use search to find number directly
+ $ids = $this->getUniqueId();
+ foreach ($ids as $k => $v) {
+ if ($v == $id) {
+ return $k;
+ }
+ }
+
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('unique id not found');
+ }
+
+
+ /**
+ * get root folder or given folder
+ *
+ * @param string $rootFolder get folder structure for given folder, else root
+ * @return Zend_Mail_Storage_Folder root or wanted folder
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getFolders($rootFolder = null)
+ {
+ $folders = $this->_protocol->listMailbox((string)$rootFolder);
+ if (!$folders) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('folder not found');
+ }
+
+ ksort($folders, SORT_STRING);
+ $root = new Zend_Mail_Storage_Folder('/', '/', false);
+ $stack = array(null);
+ $folderStack = array(null);
+ $parentFolder = $root;
+ $parent = '';
+
+ foreach ($folders as $globalName => $data) {
+ do {
+ if (!$parent || strpos($globalName, $parent) === 0) {
+ $pos = strrpos($globalName, $data['delim']);
+ if ($pos === false) {
+ $localName = $globalName;
+ } else {
+ $localName = substr($globalName, $pos + 1);
+ }
+ $selectable = !$data['flags'] || !in_array('\\Noselect', $data['flags']);
+
+ array_push($stack, $parent);
+ $parent = $globalName . $data['delim'];
+ $folder = new Zend_Mail_Storage_Folder($localName, $globalName, $selectable);
+ $parentFolder->$localName = $folder;
+ array_push($folderStack, $parentFolder);
+ $parentFolder = $folder;
+ break;
+ } else if ($stack) {
+ $parent = array_pop($stack);
+ $parentFolder = array_pop($folderStack);
+ }
+ } while ($stack);
+ if (!$stack) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('error while constructing folder tree');
+ }
+ }
+
+ return $root;
+ }
+
+ /**
+ * select given folder
+ *
+ * folder must be selectable!
+ *
+ * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function selectFolder($globalName)
+ {
+ $this->_currentFolder = $globalName;
+ if (!$this->_protocol->select($this->_currentFolder)) {
+ $this->_currentFolder = '';
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot change folder, maybe it does not exist');
+ }
+ }
+
+
+ /**
+ * get Zend_Mail_Storage_Folder instance for current folder
+ *
+ * @return Zend_Mail_Storage_Folder instance of current folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getCurrentFolder()
+ {
+ return $this->_currentFolder;
+ }
+
+ /**
+ * create a new folder
+ *
+ * This method also creates parent folders if necessary. Some mail storages may restrict, which folder
+ * may be used as parent or which chars may be used in the folder name
+ *
+ * @param string $name global name of folder, local name if $parentFolder is set
+ * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function createFolder($name, $parentFolder = null)
+ {
+ // TODO: we assume / as the hierarchy delim - need to get that from the folder class!
+ if ($parentFolder instanceof Zend_Mail_Storage_Folder) {
+ $folder = $parentFolder->getGlobalName() . '/' . $name;
+ } else if ($parentFolder != null) {
+ $folder = $parentFolder . '/' . $name;
+ } else {
+ $folder = $name;
+ }
+
+ if (!$this->_protocol->create($folder)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot create folder');
+ }
+ }
+
+ /**
+ * remove a folder
+ *
+ * @param string|Zend_Mail_Storage_Folder $name name or instance of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeFolder($name)
+ {
+ if ($name instanceof Zend_Mail_Storage_Folder) {
+ $name = $name->getGlobalName();
+ }
+
+ if (!$this->_protocol->delete($name)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot delete folder');
+ }
+ }
+
+ /**
+ * rename and/or move folder
+ *
+ * The new name has the same restrictions as in createFolder()
+ *
+ * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder
+ * @param string $newName new global name of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function renameFolder($oldName, $newName)
+ {
+ if ($oldName instanceof Zend_Mail_Storage_Folder) {
+ $oldName = $oldName->getGlobalName();
+ }
+
+ if (!$this->_protocol->rename($oldName, $newName)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot rename folder');
+ }
+ }
+
+ /**
+ * append a new message to mail storage
+ *
+ * @param string $message message as string or instance of message class
+ * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken
+ * @param null|array $flags set flags for new message, else a default set is used
+ * @throws Zend_Mail_Storage_Exception
+ */
+ // not yet * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class
+ public function appendMessage($message, $folder = null, $flags = null)
+ {
+ if ($folder === null) {
+ $folder = $this->_currentFolder;
+ }
+
+ if ($flags === null) {
+ $flags = array(Zend_Mail_Storage::FLAG_SEEN);
+ }
+
+ // TODO: handle class instances for $message
+ if (!$this->_protocol->append($folder, $message, $flags)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot create message, please check if the folder exists and your flags');
+ }
+ }
+
+ /**
+ * copy an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function copyMessage($id, $folder)
+ {
+ if (!$this->_protocol->copy($folder, $id)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot copy message, does the folder exist?');
+ }
+ }
+
+ /**
+ * move an existing message
+ *
+ * NOTE: imap has no native move command, thus it's emulated with copy and delete
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function moveMessage($id, $folder) {
+ $this->copyMessage($id, $folder);
+ $this->removeMessage($id);
+ }
+
+ /**
+ * set flags for message
+ *
+ * NOTE: this method can't set the recent flag.
+ *
+ * @param int $id number of message
+ * @param array $flags new flags for message
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function setFlags($id, $flags)
+ {
+ if (!$this->_protocol->store($flags, $id)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot set flags, have you tried to set the recent flag or special chars?');
+ }
+ }
+}
+
diff --git a/lib/Zend/Mail/Storage/Maildir.php b/lib/Zend/Mail/Storage/Maildir.php
new file mode 100644
index 0000000..429636a
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Maildir.php
@@ -0,0 +1,475 @@
+<?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 Storage
+ * @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: Maildir.php 12519 2008-11-10 18:41:24Z alexander $
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Abstract
+ */
+require_once 'Zend/Mail/Storage/Abstract.php';
+
+/**
+ * @see Zend_Mail_Message_File
+ */
+require_once 'Zend/Mail/Message/File.php';
+
+/**
+ * @see Zend_Mail_Storage
+ */
+require_once 'Zend/Mail/Storage.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Maildir extends Zend_Mail_Storage_Abstract
+{
+ /**
+ * used message class, change it in an extened class to extend the returned message class
+ * @var string
+ */
+ protected $_messageClass = 'Zend_Mail_Message_File';
+
+ /**
+ * data of found message files in maildir dir
+ * @var array
+ */
+ protected $_files = array();
+
+ /**
+ * known flag chars in filenames
+ *
+ * This list has to be in alphabetical order for setFlags()
+ *
+ * @var array
+ */
+ protected static $_knownFlags = array('D' => Zend_Mail_Storage::FLAG_DRAFT,
+ 'F' => Zend_Mail_Storage::FLAG_FLAGGED,
+ 'P' => Zend_Mail_Storage::FLAG_PASSED,
+ 'R' => Zend_Mail_Storage::FLAG_ANSWERED,
+ 'S' => Zend_Mail_Storage::FLAG_SEEN,
+ 'T' => Zend_Mail_Storage::FLAG_DELETED);
+
+ // TODO: getFlags($id) for fast access if headers are not needed (i.e. just setting flags)?
+
+ /**
+ * Count messages all messages in current box
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function countMessages($flags = null)
+ {
+ if ($flags === null) {
+ return count($this->_files);
+ }
+
+ $count = 0;
+ if (!is_array($flags)) {
+ foreach ($this->_files as $file) {
+ if (isset($file['flaglookup'][$flags])) {
+ ++$count;
+ }
+ }
+ return $count;
+ }
+
+ $flags = array_flip($flags);
+ foreach ($this->_files as $file) {
+ foreach ($flags as $flag => $v) {
+ if (!isset($file['flaglookup'][$flag])) {
+ continue 2;
+ }
+ }
+ ++$count;
+ }
+ return $count;
+ }
+
+ /**
+ * Get one or all fields from file structure. Also checks if message is valid
+ *
+ * @param int $id message number
+ * @param string|null $field wanted field
+ * @return string|array wanted field or all fields as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _getFileData($id, $field = null)
+ {
+ if (!isset($this->_files[$id - 1])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('id does not exist');
+ }
+
+ if (!$field) {
+ return $this->_files[$id - 1];
+ }
+
+ if (!isset($this->_files[$id - 1][$field])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('field does not exist');
+ }
+
+ return $this->_files[$id - 1][$field];
+ }
+
+ /**
+ * Get a list of messages with number and size
+ *
+ * @param int|null $id number of message or null for all messages
+ * @return int|array size of given message of list with all messages as array(num => size)
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getSize($id = null)
+ {
+ if ($id !== null) {
+ $filedata = $this->_getFileData($id);
+ return isset($filedata['size']) ? $filedata['size'] : filesize($filedata['filename']);
+ }
+
+ $result = array();
+ foreach ($this->_files as $num => $data) {
+ $result[$num + 1] = isset($data['size']) ? $data['size'] : filesize($data['filename']);
+ }
+
+ return $result;
+ }
+
+
+
+ /**
+ * Fetch a message
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message_File
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getMessage($id)
+ {
+ // TODO that's ugly, would be better to let the message class decide
+ if (strtolower($this->_messageClass) == 'zend_mail_message_file' || is_subclass_of($this->_messageClass, 'zend_mail_message_file')) {
+ return new $this->_messageClass(array('file' => $this->_getFileData($id, 'filename'),
+ 'flags' => $this->_getFileData($id, 'flags')));
+ }
+
+ return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $this->getRawHeader($id),
+ 'flags' => $this->_getFileData($id, 'flags')));
+ }
+
+ /*
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawHeader($id, $part = null, $topLines = 0)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ $fh = fopen($this->_getFileData($id, 'filename'), 'r');
+
+ $content = '';
+ while (!feof($fh)) {
+ $line = fgets($fh);
+ if (!trim($line)) {
+ break;
+ }
+ $content .= $line;
+ }
+
+ fclose($fh);
+ return $content;
+ }
+
+ /*
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawContent($id, $part = null)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ $fh = fopen($this->_getFileData($id, 'filename'), 'r');
+
+ while (!feof($fh)) {
+ $line = fgets($fh);
+ if (!trim($line)) {
+ break;
+ }
+ }
+
+ $content = stream_get_contents($fh);
+ fclose($fh);
+ return $content;
+ }
+
+ /**
+ * Create instance with parameters
+ * Supported parameters are:
+ * - dirname dirname of mbox file
+ *
+ * @param $params array mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (!isset($params->dirname) || !is_dir($params->dirname)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
+ }
+
+ if (!$this->_isMaildir($params->dirname)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('invalid maildir given');
+ }
+
+ $this->_has['top'] = true;
+ $this->_has['flags'] = true;
+ $this->_openMaildir($params->dirname);
+ }
+
+ /**
+ * check if a given dir is a valid maildir
+ *
+ * @param string $dirname name of dir
+ * @return bool dir is valid maildir
+ */
+ protected function _isMaildir($dirname)
+ {
+ if (file_exists($dirname . '/new') && !is_dir($dirname . '/new')) {
+ return false;
+ }
+ if (file_exists($dirname . '/tmp') && !is_dir($dirname . '/tmp')) {
+ return false;
+ }
+ return is_dir($dirname . '/cur');
+ }
+
+ /**
+ * open given dir as current maildir
+ *
+ * @param string $dirname name of maildir
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _openMaildir($dirname)
+ {
+ if ($this->_files) {
+ $this->close();
+ }
+
+ $dh = @opendir($dirname . '/cur/');
+ if (!$dh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot open maildir');
+ }
+ $this->_getMaildirFiles($dh, $dirname . '/cur/');
+ closedir($dh);
+
+ $dh = @opendir($dirname . '/new/');
+ if ($dh) {
+ $this->_getMaildirFiles($dh, $dirname . '/new/', array(Zend_Mail_Storage::FLAG_RECENT));
+ closedir($dh);
+ } else if (file_exists($dirname . '/new/')) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot read recent mails in maildir');
+ }
+ }
+
+ /**
+ * find all files in opened dir handle and add to maildir files
+ *
+ * @param resource $dh dir handle used for search
+ * @param string $dirname dirname of dir in $dh
+ * @param array $default_flags default flags for given dir
+ * @return null
+ */
+ protected function _getMaildirFiles($dh, $dirname, $default_flags = array())
+ {
+ while (($entry = readdir($dh)) !== false) {
+ if ($entry[0] == '.' || !is_file($dirname . $entry)) {
+ continue;
+ }
+
+ @list($uniq, $info) = explode(':', $entry, 2);
+ @list(,$size) = explode(',', $uniq, 2);
+ if ($size && $size[0] == 'S' && $size[1] == '=') {
+ $size = substr($size, 2);
+ }
+ if (!ctype_digit($size)) {
+ $size = null;
+ }
+ @list($version, $flags) = explode(',', $info, 2);
+ if ($version != 2) {
+ $flags = '';
+ }
+
+ $named_flags = $default_flags;
+ $length = strlen($flags);
+ for ($i = 0; $i < $length; ++$i) {
+ $flag = $flags[$i];
+ $named_flags[$flag] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag;
+ }
+
+ $data = array('uniq' => $uniq,
+ 'flags' => $named_flags,
+ 'flaglookup' => array_flip($named_flags),
+ 'filename' => $dirname . $entry);
+ if ($size !== null) {
+ $data['size'] = (int)$size;
+ }
+ $this->_files[] = $data;
+ }
+ }
+
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return void
+ */
+ public function close()
+ {
+ $this->_files = array();
+ }
+
+
+ /**
+ * Waste some CPU cycles doing nothing.
+ *
+ * @return void
+ */
+ public function noop()
+ {
+ return true;
+ }
+
+
+ /**
+ * stub for not supported message deletion
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeMessage($id)
+ {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('maildir is (currently) read-only');
+ }
+
+ /**
+ * get unique id for one or all messages
+ *
+ * if storage does not support unique ids it's the same as the message number
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getUniqueId($id = null)
+ {
+ if ($id) {
+ return $this->_getFileData($id, 'uniq');
+ }
+
+ $ids = array();
+ foreach ($this->_files as $num => $file) {
+ $ids[$num + 1] = $file['uniq'];
+ }
+ return $ids;
+ }
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getNumberByUniqueId($id)
+ {
+ foreach ($this->_files as $num => $file) {
+ if ($file['uniq'] == $id) {
+ return $num + 1;
+ }
+ }
+
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('unique id not found');
+ }
+}
diff --git a/lib/Zend/Mail/Storage/Mbox.php b/lib/Zend/Mail/Storage/Mbox.php
new file mode 100644
index 0000000..8be42d8
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Mbox.php
@@ -0,0 +1,447 @@
+<?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 Storage
+ * @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: Mbox.php 12519 2008-11-10 18:41:24Z alexander $
+ */
+
+
+/**
+ * @see Zend_Loader
+ * May be used in constructor, but commented out for now
+ */
+// require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Mail_Storage_Abstract
+ */
+require_once 'Zend/Mail/Storage/Abstract.php';
+
+/**
+ * @see Zend_Mail_Message_File
+ */
+require_once 'Zend/Mail/Message/File.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Mbox extends Zend_Mail_Storage_Abstract
+{
+ /**
+ * file handle to mbox file
+ * @var null|resource
+ */
+ protected $_fh;
+
+ /**
+ * filename of mbox file for __wakeup
+ * @var string
+ */
+ protected $_filename;
+
+ /**
+ * modification date of mbox file for __wakeup
+ * @var int
+ */
+ protected $_filemtime;
+
+ /**
+ * start and end position of messages as array('start' => start, 'seperator' => headersep, 'end' => end)
+ * @var array
+ */
+ protected $_positions;
+
+ /**
+ * used message class, change it in an extened class to extend the returned message class
+ * @var string
+ */
+ protected $_messageClass = 'Zend_Mail_Message_File';
+
+ /**
+ * Count messages all messages in current box
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function countMessages()
+ {
+ return count($this->_positions);
+ }
+
+
+ /**
+ * Get a list of messages with number and size
+ *
+ * @param int|null $id number of message or null for all messages
+ * @return int|array size of given message of list with all messages as array(num => size)
+ */
+ public function getSize($id = 0)
+ {
+ if ($id) {
+ $pos = $this->_positions[$id - 1];
+ return $pos['end'] - $pos['start'];
+ }
+
+ $result = array();
+ foreach ($this->_positions as $num => $pos) {
+ $result[$num + 1] = $pos['end'] - $pos['start'];
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Get positions for mail message or throw exeption if id is invalid
+ *
+ * @param int $id number of message
+ * @return array positions as in _positions
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _getPos($id)
+ {
+ if (!isset($this->_positions[$id - 1])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('id does not exist');
+ }
+
+ return $this->_positions[$id - 1];
+ }
+
+
+ /**
+ * Fetch a message
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message_File
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getMessage($id)
+ {
+ // TODO that's ugly, would be better to let the message class decide
+ if (strtolower($this->_messageClass) == 'zend_mail_message_file' || is_subclass_of($this->_messageClass, 'zend_mail_message_file')) {
+ // TODO top/body lines
+ $messagePos = $this->_getPos($id);
+ return new $this->_messageClass(array('file' => $this->_fh, 'startPos' => $messagePos['start'],
+ 'endPos' => $messagePos['end']));
+ }
+
+ $bodyLines = 0; // TODO: need a way to change that
+
+ $message = $this->getRawHeader($id);
+ // file pointer is after headers now
+ if ($bodyLines) {
+ $message .= "\n";
+ while ($bodyLines-- && ftell($this->_fh) < $this->_positions[$id - 1]['end']) {
+ $message .= fgets($this->_fh);
+ }
+ }
+
+ return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $message));
+ }
+
+ /*
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawHeader($id, $part = null, $topLines = 0)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+ $messagePos = $this->_getPos($id);
+ // TODO: toplines
+ return stream_get_contents($this->_fh, $messagePos['separator'] - $messagePos['start'], $messagePos['start']);
+ }
+
+ /*
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawContent($id, $part = null)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+ $messagePos = $this->_getPos($id);
+ return stream_get_contents($this->_fh, $messagePos['end'] - $messagePos['separator'], $messagePos['separator']);
+ }
+
+ /**
+ * Create instance with parameters
+ * Supported parameters are:
+ * - filename filename of mbox file
+ *
+ * @param $params array mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (!isset($params->filename) /* || Zend_Loader::isReadable($params['filename']) */) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('no valid filename given in params');
+ }
+
+ $this->_openMboxFile($params->filename);
+ $this->_has['top'] = true;
+ $this->_has['uniqueid'] = false;
+ }
+
+ /**
+ * check if given file is a mbox file
+ *
+ * if $file is a resource its file pointer is moved after the first line
+ *
+ * @param resource|string $file stream resource of name of file
+ * @param bool $fileIsString file is string or resource
+ * @return bool file is mbox file
+ */
+ protected function _isMboxFile($file, $fileIsString = true)
+ {
+ if ($fileIsString) {
+ $file = @fopen($file, 'r');
+ if (!$file) {
+ return false;
+ }
+ } else {
+ fseek($file, 0);
+ }
+
+ $result = false;
+
+ $line = fgets($file);
+ if (strpos($line, 'From ') === 0) {
+ $result = true;
+ }
+
+ if ($fileIsString) {
+ @fclose($file);
+ }
+
+ return $result;
+ }
+
+ /**
+ * open given file as current mbox file
+ *
+ * @param string $filename filename of mbox file
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _openMboxFile($filename)
+ {
+ if ($this->_fh) {
+ $this->close();
+ }
+
+ $this->_fh = @fopen($filename, 'r');
+ if (!$this->_fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot open mbox file');
+ }
+ $this->_filename = $filename;
+ $this->_filemtime = filemtime($this->_filename);
+
+ if (!$this->_isMboxFile($this->_fh, false)) {
+ @fclose($this->_fh);
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('file is not a valid mbox format');
+ }
+
+ $messagePos = array('start' => ftell($this->_fh), 'separator' => 0, 'end' => 0);
+ while (($line = fgets($this->_fh)) !== false) {
+ if (strpos($line, 'From ') === 0) {
+ $messagePos['end'] = ftell($this->_fh) - strlen($line) - 2; // + newline
+ if (!$messagePos['separator']) {
+ $messagePos['separator'] = $messagePos['end'];
+ }
+ $this->_positions[] = $messagePos;
+ $messagePos = array('start' => ftell($this->_fh), 'separator' => 0, 'end' => 0);
+ }
+ if (!$messagePos['separator'] && !trim($line)) {
+ $messagePos['separator'] = ftell($this->_fh);
+ }
+ }
+
+ $messagePos['end'] = ftell($this->_fh);
+ if (!$messagePos['separator']) {
+ $messagePos['separator'] = $messagePos['end'];
+ }
+ $this->_positions[] = $messagePos;
+ }
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return void
+ */
+ public function close()
+ {
+ @fclose($this->_fh);
+ $this->_positions = array();
+ }
+
+
+ /**
+ * Waste some CPU cycles doing nothing.
+ *
+ * @return void
+ */
+ public function noop()
+ {
+ return true;
+ }
+
+
+ /**
+ * stub for not supported message deletion
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeMessage($id)
+ {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('mbox is read-only');
+ }
+
+ /**
+ * get unique id for one or all messages
+ *
+ * Mbox does not support unique ids (yet) - it's always the same as the message number.
+ * That shouldn't be a problem, because we can't change mbox files. Therefor the message
+ * number is save enough.
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getUniqueId($id = null)
+ {
+ if ($id) {
+ // check if id exists
+ $this->_getPos($id);
+ return $id;
+ }
+
+ $range = range(1, $this->countMessages());
+ return array_combine($range, $range);
+ }
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getNumberByUniqueId($id)
+ {
+ // check if id exists
+ $this->_getPos($id);
+ return $id;
+ }
+
+ /**
+ * magic method for serialize()
+ *
+ * with this method you can cache the mbox class
+ *
+ * @return array name of variables
+ */
+ public function __sleep()
+ {
+ return array('_filename', '_positions', '_filemtime');
+ }
+
+ /**
+ * magic method for unserialize()
+ *
+ * with this method you can cache the mbox class
+ * for cache validation the mtime of the mbox file is used
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __wakeup()
+ {
+ if ($this->_filemtime != @filemtime($this->_filename)) {
+ $this->close();
+ $this->_openMboxFile($this->_filename);
+ } else {
+ $this->_fh = @fopen($this->_filename, 'r');
+ if (!$this->_fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot open mbox file');
+ }
+ }
+ }
+
+}
diff --git a/lib/Zend/Mail/Storage/Pop3.php b/lib/Zend/Mail/Storage/Pop3.php
new file mode 100644
index 0000000..693384d
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Pop3.php
@@ -0,0 +1,328 @@
+<?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 Storage
+ * @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 9099 2008-03-30 19:35:47Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Abstract
+ */
+require_once 'Zend/Mail/Storage/Abstract.php';
+
+/**
+ * @see Zend_Mail_Protocol_Pop3
+ */
+require_once 'Zend/Mail/Protocol/Pop3.php';
+
+/**
+ * @see Zend_Mail_Message
+ */
+require_once 'Zend/Mail/Message.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Pop3 extends Zend_Mail_Storage_Abstract
+{
+ /**
+ * protocol handler
+ * @var null|Zend_Mail_Protocol_Pop3
+ */
+ protected $_protocol;
+
+
+ /**
+ * Count messages all messages in current box
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function countMessages()
+ {
+ $this->_protocol->status($count, $null);
+ return (int)$count;
+ }
+
+ /**
+ * get a list of messages with number and size
+ *
+ * @param int $id number of message
+ * @return int|array size of given message of list with all messages as array(num => size)
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getSize($id = 0)
+ {
+ $id = $id ? $id : null;
+ return $this->_protocol->getList($id);
+ }
+
+ /**
+ * Fetch a message
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getMessage($id)
+ {
+ $bodyLines = 0;
+ $message = $this->_protocol->top($id, $bodyLines, true);
+
+ return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $message,
+ 'noToplines' => $bodyLines < 1));
+ }
+
+ /*
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawHeader($id, $part = null, $topLines = 0)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ return $this->_protocol->top($id, 0, true);
+ }
+
+ /*
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawContent($id, $part = null)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ $content = $this->_protocol->retrieve($id);
+ // TODO: find a way to avoid decoding the headers
+ Zend_Mime_Decode::splitMessage($content, $null, $body);
+ return $body;
+ }
+
+ /**
+ * create instance with parameters
+ * Supported paramters are
+ * - host hostname or ip address of POP3 server
+ * - user username
+ * - password password for user 'username' [optional, default = '']
+ * - port port for POP3 server [optional, default = 110]
+ * - ssl 'SSL' or 'TLS' for secure sockets
+ *
+ * @param $params array mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ $this->_has['fetchPart'] = false;
+ $this->_has['top'] = null;
+ $this->_has['uniqueid'] = null;
+
+ if ($params instanceof Zend_Mail_Protocol_Pop3) {
+ $this->_protocol = $params;
+ return;
+ }
+
+ if (!isset($params->user)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('need at least user in params');
+ }
+
+ $host = isset($params->host) ? $params->host : 'localhost';
+ $password = isset($params->password) ? $params->password : '';
+ $port = isset($params->port) ? $params->port : null;
+ $ssl = isset($params->ssl) ? $params->ssl : false;
+
+ $this->_protocol = new Zend_Mail_Protocol_Pop3();
+ $this->_protocol->connect($host, $port, $ssl);
+ $this->_protocol->login($params->user, $password);
+ }
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return null
+ */
+ public function close()
+ {
+ $this->_protocol->logout();
+ }
+
+ /**
+ * Keep the server busy.
+ *
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function noop()
+ {
+ return $this->_protocol->noop();
+ }
+
+ /**
+ * Remove a message from server. If you're doing that from a web enviroment
+ * you should be careful and use a uniqueid as parameter if possible to
+ * identify the message.
+ *
+ * @param int $id number of message
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function removeMessage($id)
+ {
+ $this->_protocol->delete($id);
+ }
+
+ /**
+ * get unique id for one or all messages
+ *
+ * if storage does not support unique ids it's the same as the message number
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getUniqueId($id = null)
+ {
+ if (!$this->hasUniqueid) {
+ if ($id) {
+ return $id;
+ }
+ $count = $this->countMessages();
+ if ($count < 1) {
+ return array();
+ }
+ $range = range(1, $count);
+ return array_combine($range, $range);
+ }
+
+ return $this->_protocol->uniqueid($id);
+ }
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getNumberByUniqueId($id)
+ {
+ if (!$this->hasUniqueid) {
+ return $id;
+ }
+
+ $ids = $this->getUniqueId();
+ foreach ($ids as $k => $v) {
+ if ($v == $id) {
+ return $k;
+ }
+ }
+
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('unique id not found');
+ }
+
+ /**
+ * Special handling for hasTop and hasUniqueid. The headers of the first message is
+ * retrieved if Top wasn't needed/tried yet.
+ *
+ * @see Zend_Mail_Storage_Abstract:__get()
+ * @param string $var
+ * @return string
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __get($var)
+ {
+ $result = parent::__get($var);
+ if ($result !== null) {
+ return $result;
+ }
+
+ if (strtolower($var) == 'hastop') {
+ if ($this->_protocol->hasTop === null) {
+ // need to make a real call, because not all server are honest in their capas
+ try {
+ $this->_protocol->top(1, 0, false);
+ } catch(Zend_Mail_Exception $e) {
+ // ignoring error
+ }
+ }
+ $this->_has['top'] = $this->_protocol->hasTop;
+ return $this->_protocol->hasTop;
+ }
+
+ if (strtolower($var) == 'hasuniqueid') {
+ $id = null;
+ try {
+ $id = $this->_protocol->uniqueid(1);
+ } catch(Zend_Mail_Exception $e) {
+ // ignoring error
+ }
+ $this->_has['uniqueid'] = $id ? true : false;
+ return $this->_has['uniqueid'];
+ }
+
+ return $result;
+ }
+}
diff --git a/lib/Zend/Mail/Storage/Writable/Interface.php b/lib/Zend/Mail/Storage/Writable/Interface.php
new file mode 100644
index 0000000..b49fc8e
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Writable/Interface.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 Storage
+ * @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: Interface.php 9098 2008-03-30 19:29:10Z thomas $
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+interface Zend_Mail_Storage_Writable_Interface
+{
+ /**
+ * create a new folder
+ *
+ * This method also creates parent folders if necessary. Some mail storages may restrict, which folder
+ * may be used as parent or which chars may be used in the folder name
+ *
+ * @param string $name global name of folder, local name if $parentFolder is set
+ * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function createFolder($name, $parentFolder = null);
+
+ /**
+ * remove a folder
+ *
+ * @param string|Zend_Mail_Storage_Folder $name name or instance of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeFolder($name);
+
+ /**
+ * rename and/or move folder
+ *
+ * The new name has the same restrictions as in createFolder()
+ *
+ * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder
+ * @param string $newName new global name of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function renameFolder($oldName, $newName);
+
+ /**
+ * append a new message to mail storage
+ *
+ * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class
+ * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken
+ * @param null|array $flags set flags for new message, else a default set is used
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function appendMessage($message, $folder = null, $flags = null);
+
+ /**
+ * copy an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function copyMessage($id, $folder);
+
+ /**
+ * move an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function moveMessage($id, $folder);
+
+ /**
+ * set flags for message
+ *
+ * NOTE: this method can't set the recent flag.
+ *
+ * @param int $id number of message
+ * @param array $flags new flags for message
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function setFlags($id, $flags);
+} \ No newline at end of file
diff --git a/lib/Zend/Mail/Storage/Writable/Maildir.php b/lib/Zend/Mail/Storage/Writable/Maildir.php
new file mode 100644
index 0000000..9ba7573
--- /dev/null
+++ b/lib/Zend/Mail/Storage/Writable/Maildir.php
@@ -0,0 +1,1049 @@
+<?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 Storage
+ * @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: Maildir.php 12519 2008-11-10 18:41:24Z alexander $
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Folder_Maildir
+ */
+require_once 'Zend/Mail/Storage/Folder/Maildir.php';
+
+/**
+ * @see Zend_Mail_Storage_Writable_Interface
+ */
+require_once 'Zend/Mail/Storage/Writable/Interface.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @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_Storage_Writable_Maildir extends Zend_Mail_Storage_Folder_Maildir
+ implements Zend_Mail_Storage_Writable_Interface
+{
+ // TODO: init maildir (+ constructor option create if not found)
+
+ /**
+ * use quota and size of quota if given
+ * @var bool|int
+ */
+ protected $_quota;
+
+ /**
+ * create a new maildir
+ *
+ * If the given dir is already a valid maildir this will not fail.
+ *
+ * @param string $dir directory for the new maildir (may already exist)
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public static function initMaildir($dir)
+ {
+ if (file_exists($dir)) {
+ if (!is_dir($dir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('maildir must be a directory if already exists');
+ }
+ } else {
+ if (!mkdir($dir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ $dir = dirname($dir);
+ if (!file_exists($dir)) {
+ throw new Zend_Mail_Storage_Exception("parent $dir not found");
+ } else if (!is_dir($dir)) {
+ throw new Zend_Mail_Storage_Exception("parent $dir not a directory");
+ } else {
+ throw new Zend_Mail_Storage_Exception('cannot create maildir');
+ }
+ }
+ }
+
+ foreach (array('cur', 'tmp', 'new') as $subdir) {
+ if (!@mkdir($dir . DIRECTORY_SEPARATOR . $subdir)) {
+ // ignore if dir exists (i.e. was already valid maildir or two processes try to create one)
+ if (!file_exists($dir . DIRECTORY_SEPARATOR . $subdir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('could not create subdir ' . $subdir);
+ }
+ }
+ }
+ }
+
+ /**
+ * Create instance with parameters
+ * Additional parameters are (see parent for more):
+ * - create if true a new maildir is create if none exists
+ *
+ * @param $params array mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params) {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (!empty($params->create) && isset($params->dirname) && !file_exists($params->dirname . DIRECTORY_SEPARATOR . 'cur')) {
+ self::initMaildir($params->dirname);
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * create a new folder
+ *
+ * This method also creates parent folders if necessary. Some mail storages may restrict, which folder
+ * may be used as parent or which chars may be used in the folder name
+ *
+ * @param string $name global name of folder, local name if $parentFolder is set
+ * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent
+ * @return string only used internally (new created maildir)
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function createFolder($name, $parentFolder = null)
+ {
+ if ($parentFolder instanceof Zend_Mail_Storage_Folder) {
+ $folder = $parentFolder->getGlobalName() . $this->_delim . $name;
+ } else if ($parentFolder != null) {
+ $folder = rtrim($parentFolder, $this->_delim) . $this->_delim . $name;
+ } else {
+ $folder = $name;
+ }
+
+ $folder = trim($folder, $this->_delim);
+
+ // first we check if we try to create a folder that does exist
+ $exists = null;
+ try {
+ $exists = $this->getFolders($folder);
+ } catch (Zend_Mail_Exception $e) {
+ // ok
+ }
+ if ($exists) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('folder already exists');
+ }
+
+ if (strpos($folder, $this->_delim . $this->_delim) !== false) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('invalid name - folder parts may not be empty');
+ }
+
+ if (strpos($folder, 'INBOX' . $this->_delim) === 0) {
+ $folder = substr($folder, 6);
+ }
+
+ $fulldir = $this->_rootdir . '.' . $folder;
+
+ // check if we got tricked and would create a dir outside of the rootdir or not as direct child
+ if (strpos($folder, DIRECTORY_SEPARATOR) !== false || strpos($folder, '/') !== false
+ || dirname($fulldir) . DIRECTORY_SEPARATOR != $this->_rootdir) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('invalid name - no directory seprator allowed in folder name');
+ }
+
+ // has a parent folder?
+ $parent = null;
+ if (strpos($folder, $this->_delim)) {
+ // let's see if the parent folder exists
+ $parent = substr($folder, 0, strrpos($folder, $this->_delim));
+ try {
+ $this->getFolders($parent);
+ } catch (Zend_Mail_Exception $e) {
+ // does not - create parent folder
+ $this->createFolder($parent);
+ }
+ }
+
+ if (!@mkdir($fulldir) || !@mkdir($fulldir . DIRECTORY_SEPARATOR . 'cur')) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('error while creating new folder, may be created incompletly');
+ }
+
+ mkdir($fulldir . DIRECTORY_SEPARATOR . 'new');
+ mkdir($fulldir . DIRECTORY_SEPARATOR . 'tmp');
+
+ $localName = $parent ? substr($folder, strlen($parent) + 1) : $folder;
+ $this->getFolders($parent)->$localName = new Zend_Mail_Storage_Folder($localName, $folder, true);
+
+ return $fulldir;
+ }
+
+ /**
+ * remove a folder
+ *
+ * @param string|Zend_Mail_Storage_Folder $name name or instance of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeFolder($name)
+ {
+ // TODO: This could fail in the middle of the task, which is not optimal.
+ // But there is no defined standard way to mark a folder as removed and there is no atomar fs-op
+ // to remove a directory. Also moving the folder to a/the trash folder is not possible, as
+ // all parent folders must be created. What we could do is add a dash to the front of the
+ // directory name and it should be ignored as long as other processes obey the standard.
+
+ if ($name instanceof Zend_Mail_Storage_Folder) {
+ $name = $name->getGlobalName();
+ }
+
+ $name = trim($name, $this->_delim);
+ if (strpos($name, 'INBOX' . $this->_delim) === 0) {
+ $name = substr($name, 6);
+ }
+
+ // check if folder exists and has no children
+ if (!$this->getFolders($name)->isLeaf()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('delete children first');
+ }
+
+ if ($name == 'INBOX' || $name == DIRECTORY_SEPARATOR || $name == '/') {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('wont delete INBOX');
+ }
+
+ if ($name == $this->getCurrentFolder()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('wont delete selected folder');
+ }
+
+ foreach (array('tmp', 'new', 'cur', '.') as $subdir) {
+ $dir = $this->_rootdir . '.' . $name . DIRECTORY_SEPARATOR . $subdir;
+ if (!file_exists($dir)) {
+ continue;
+ }
+ $dh = opendir($dir);
+ if (!$dh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("error opening $subdir");
+ }
+ while (($entry = readdir($dh)) !== false) {
+ if ($entry == '.' || $entry == '..') {
+ continue;
+ }
+ if (!unlink($dir . DIRECTORY_SEPARATOR . $entry)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("error cleaning $subdir");
+ }
+ }
+ closedir($dh);
+ if ($subdir !== '.') {
+ if (!rmdir($dir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("error removing $subdir");
+ }
+ }
+ }
+
+ if (!rmdir($this->_rootdir . '.' . $name)) {
+ // at least we should try to make it a valid maildir again
+ mkdir($this->_rootdir . '.' . $name . DIRECTORY_SEPARATOR . 'cur');
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("error removing maindir");
+ }
+
+ $parent = strpos($name, $this->_delim) ? substr($name, 0, strrpos($name, $this->_delim)) : null;
+ $localName = $parent ? substr($name, strlen($parent) + 1) : $name;
+ unset($this->getFolders($parent)->$localName);
+ }
+
+ /**
+ * rename and/or move folder
+ *
+ * The new name has the same restrictions as in createFolder()
+ *
+ * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder
+ * @param string $newName new global name of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function renameFolder($oldName, $newName)
+ {
+ // TODO: This is also not atomar and has similar problems as removeFolder()
+
+ if ($oldName instanceof Zend_Mail_Storage_Folder) {
+ $oldName = $oldName->getGlobalName();
+ }
+
+ $oldName = trim($oldName, $this->_delim);
+ if (strpos($oldName, 'INBOX' . $this->_delim) === 0) {
+ $oldName = substr($oldName, 6);
+ }
+
+ $newName = trim($newName, $this->_delim);
+ if (strpos($newName, 'INBOX' . $this->_delim) === 0) {
+ $newName = substr($newName, 6);
+ }
+
+ if (strpos($newName, $oldName . $this->_delim) === 0) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('new folder cannot be a child of old folder');
+ }
+
+ // check if folder exists and has no children
+ $folder = $this->getFolders($oldName);
+
+ if ($oldName == 'INBOX' || $oldName == DIRECTORY_SEPARATOR || $oldName == '/') {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('wont rename INBOX');
+ }
+
+ if ($oldName == $this->getCurrentFolder()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('wont rename selected folder');
+ }
+
+ $newdir = $this->createFolder($newName);
+
+ if (!$folder->isLeaf()) {
+ foreach ($folder as $k => $v) {
+ $this->renameFolder($v->getGlobalName(), $newName . $this->_delim . $k);
+ }
+ }
+
+ $olddir = $this->_rootdir . '.' . $folder;
+ foreach (array('tmp', 'new', 'cur') as $subdir) {
+ $subdir = DIRECTORY_SEPARATOR . $subdir;
+ if (!file_exists($olddir . $subdir)) {
+ continue;
+ }
+ // using copy or moving files would be even better - but also much slower
+ if (!rename($olddir . $subdir, $newdir . $subdir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('error while moving ' . $subdir);
+ }
+ }
+ // create a dummy if removing fails - otherwise we can't read it next time
+ mkdir($olddir . DIRECTORY_SEPARATOR . 'cur');
+ $this->removeFolder($oldName);
+ }
+
+ /**
+ * create a uniqueid for maildir filename
+ *
+ * This is nearly the format defined in the maildir standard. The microtime() call should already
+ * create a uniqueid, the pid is for multicore/-cpu machine that manage to call this function at the
+ * exact same time, and uname() gives us the hostname for multiple machines accessing the same storage.
+ *
+ * If someone disables posix we create a random number of the same size, so this method should also
+ * work on Windows - if you manage to get maildir working on Windows.
+ * Microtime could also be disabled, altough I've never seen it.
+ *
+ * @return string new uniqueid
+ */
+ protected function _createUniqueId()
+ {
+ $id = '';
+ $id .= function_exists('microtime') ? microtime(true) : (time() . ' ' . rand(0, 100000));
+ $id .= '.' . (function_exists('posix_getpid') ? posix_getpid() : rand(50, 65535));
+ $id .= '.' . php_uname('n');
+
+ return $id;
+ }
+
+ /**
+ * open a temporary maildir file
+ *
+ * makes sure tmp/ exists and create a file with a unique name
+ * you should close the returned filehandle!
+ *
+ * @param string $folder name of current folder without leading .
+ * @return array array('dirname' => dir of maildir folder, 'uniq' => unique id, 'filename' => name of create file
+ * 'handle' => file opened for writing)
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _createTmpFile($folder = 'INBOX')
+ {
+ if ($folder == 'INBOX') {
+ $tmpdir = $this->_rootdir . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
+ } else {
+ $tmpdir = $this->_rootdir . '.' . $folder . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
+ }
+ if (!file_exists($tmpdir)) {
+ if (!mkdir($tmpdir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('problems creating tmp dir');
+ }
+ }
+
+ // we should retry to create a unique id if a file with the same name exists
+ // to avoid a script timeout we only wait 1 second (instead of 2) and stop
+ // after a defined retry count
+ // if you change this variable take into account that it can take up to $max_tries seconds
+ // normally we should have a valid unique name after the first try, we're just following the "standard" here
+ $max_tries = 5;
+ for ($i = 0; $i < $max_tries; ++$i) {
+ $uniq = $this->_createUniqueId();
+ if (!file_exists($tmpdir . $uniq)) {
+ // here is the race condition! - as defined in the standard
+ // to avoid having a long time between stat()ing the file and creating it we're opening it here
+ // to mark the filename as taken
+ $fh = fopen($tmpdir . $uniq, 'w');
+ if (!$fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('could not open temp file');
+ }
+ break;
+ }
+ sleep(1);
+ }
+
+ if (!$fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception("tried $max_tries unique ids for a temp file, but all were taken"
+ . ' - giving up');
+ }
+
+ return array('dirname' => $this->_rootdir . '.' . $folder, 'uniq' => $uniq, 'filename' => $tmpdir . $uniq,
+ 'handle' => $fh);
+ }
+
+ /**
+ * create an info string for filenames with given flags
+ *
+ * @param array $flags wanted flags, with the reference you'll get the set flags with correct key (= char for flag)
+ * @return string info string for version 2 filenames including the leading colon
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _getInfoString(&$flags)
+ {
+ // accessing keys is easier, faster and it removes duplicated flags
+ $wanted_flags = array_flip($flags);
+ if (isset($wanted_flags[Zend_Mail_Storage::FLAG_RECENT])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('recent flag may not be set');
+ }
+
+ $info = ':2,';
+ $flags = array();
+ foreach (Zend_Mail_Storage_Maildir::$_knownFlags as $char => $flag) {
+ if (!isset($wanted_flags[$flag])) {
+ continue;
+ }
+ $info .= $char;
+ $flags[$char] = $flag;
+ unset($wanted_flags[$flag]);
+ }
+
+ if (!empty($wanted_flags)) {
+ $wanted_flags = implode(', ', array_keys($wanted_flags));
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('unknown flag(s): ' . $wanted_flags);
+ }
+
+ return $info;
+ }
+
+ /**
+ * append a new message to mail storage
+ *
+ * @param string|stream $message message as string or stream resource
+ * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken
+ * @param null|array $flags set flags for new message, else a default set is used
+ * @param bool $recent handle this mail as if recent flag has been set,
+ * should only be used in delivery
+ * @throws Zend_Mail_Storage_Exception
+ */
+ // not yet * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class
+
+ public function appendMessage($message, $folder = null, $flags = null, $recent = false)
+ {
+ if ($this->_quota && $this->checkQuota()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('storage is over quota!');
+ }
+
+ if ($folder === null) {
+ $folder = $this->_currentFolder;
+ }
+
+ if (!($folder instanceof Zend_Mail_Storage_Folder)) {
+ $folder = $this->getFolders($folder);
+ }
+
+ if ($flags === null) {
+ $flags = array(Zend_Mail_Storage::FLAG_SEEN);
+ }
+ $info = $this->_getInfoString($flags);
+ $temp_file = $this->_createTmpFile($folder->getGlobalName());
+
+ // TODO: handle class instances for $message
+ if (is_resource($message) && get_resource_type($message) == 'stream') {
+ stream_copy_to_stream($message, $temp_file['handle']);
+ } else {
+ fputs($temp_file['handle'], $message);
+ }
+ fclose($temp_file['handle']);
+
+ // we're adding the size to the filename for maildir++
+ $size = filesize($temp_file['filename']);
+ if ($size !== false) {
+ $info = ',S=' . $size . $info;
+ }
+ $new_filename = $temp_file['dirname'] . DIRECTORY_SEPARATOR;
+ $new_filename .= $recent ? 'new' : 'cur';
+ $new_filename .= DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info;
+
+ // we're throwing any exception after removing our temp file and saving it to this variable instead
+ $exception = null;
+
+ if (!link($temp_file['filename'], $new_filename)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ $exception = new Zend_Mail_Storage_Exception('cannot link message file to final dir');
+ }
+ @unlink($temp_file['filename']);
+
+ if ($exception) {
+ throw $exception;
+ }
+
+ $this->_files[] = array('uniq' => $temp_file['uniq'],
+ 'flags' => $flags,
+ 'filename' => $new_filename);
+ if ($this->_quota) {
+ $this->_addQuotaEntry((int)$size, 1);
+ }
+ }
+
+ /**
+ * copy an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function copyMessage($id, $folder)
+ {
+ if ($this->_quota && $this->checkQuota()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('storage is over quota!');
+ }
+
+ if (!($folder instanceof Zend_Mail_Storage_Folder)) {
+ $folder = $this->getFolders($folder);
+ }
+
+ $filedata = $this->_getFileData($id);
+ $old_file = $filedata['filename'];
+ $flags = $filedata['flags'];
+
+ // copied message can't be recent
+ while (($key = array_search(Zend_Mail_Storage::FLAG_RECENT, $flags)) !== false) {
+ unset($flags[$key]);
+ }
+ $info = $this->_getInfoString($flags);
+
+ // we're creating the copy as temp file before moving to cur/
+ $temp_file = $this->_createTmpFile($folder->getGlobalName());
+ // we don't write directly to the file
+ fclose($temp_file['handle']);
+
+ // we're adding the size to the filename for maildir++
+ $size = filesize($old_file);
+ if ($size !== false) {
+ $info = ',S=' . $size . $info;
+ }
+
+ $new_file = $temp_file['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info;
+
+ // we're throwing any exception after removing our temp file and saving it to this variable instead
+ $exception = null;
+
+ if (!copy($old_file, $temp_file['filename'])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ $exception = new Zend_Mail_Storage_Exception('cannot copy message file');
+ } else if (!link($temp_file['filename'], $new_file)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ $exception = new Zend_Mail_Storage_Exception('cannot link message file to final dir');
+ }
+ @unlink($temp_file['filename']);
+
+ if ($exception) {
+ throw $exception;
+ }
+
+ if ($folder->getGlobalName() == $this->_currentFolder
+ || ($this->_currentFolder == 'INBOX' && $folder->getGlobalName() == '/')) {
+ $this->_files[] = array('uniq' => $temp_file['uniq'],
+ 'flags' => $flags,
+ 'filename' => $new_file);
+ }
+
+ if ($this->_quota) {
+ $this->_addQuotaEntry((int)$size, 1);
+ }
+ }
+
+ /**
+ * move an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function moveMessage($id, $folder) {
+ if (!($folder instanceof Zend_Mail_Storage_Folder)) {
+ $folder = $this->getFolders($folder);
+ }
+
+ if ($folder->getGlobalName() == $this->_currentFolder
+ || ($this->_currentFolder == 'INBOX' && $folder->getGlobalName() == '/')) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('target is current folder');
+ }
+
+ $filedata = $this->_getFileData($id);
+ $old_file = $filedata['filename'];
+ $flags = $filedata['flags'];
+
+ // moved message can't be recent
+ while (($key = array_search(Zend_Mail_Storage::FLAG_RECENT, $flags)) !== false) {
+ unset($flags[$key]);
+ }
+ $info = $this->_getInfoString($flags);
+
+ // reserving a new name
+ $temp_file = $this->_createTmpFile($folder->getGlobalName());
+ fclose($temp_file['handle']);
+
+ // we're adding the size to the filename for maildir++
+ $size = filesize($old_file);
+ if ($size !== false) {
+ $info = ',S=' . $size . $info;
+ }
+
+ $new_file = $temp_file['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info;
+
+ // we're throwing any exception after removing our temp file and saving it to this variable instead
+ $exception = null;
+
+ if (!rename($old_file, $new_file)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ $exception = new Zend_Mail_Storage_Exception('cannot move message file');
+ }
+ @unlink($temp_file['filename']);
+
+ if ($exception) {
+ throw $exception;
+ }
+
+ unset($this->_files[$id - 1]);
+ // remove the gap
+ $this->_files = array_values($this->_files);
+ }
+
+
+ /**
+ * set flags for message
+ *
+ * NOTE: this method can't set the recent flag.
+ *
+ * @param int $id number of message
+ * @param array $flags new flags for message
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function setFlags($id, $flags)
+ {
+ $info = $this->_getInfoString($flags);
+ $filedata = $this->_getFileData($id);
+
+ // NOTE: double dirname to make sure we always move to cur. if recent flag has been set (message is in new) it will be moved to cur.
+ $new_filename = dirname(dirname($filedata['filename'])) . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . "$filedata[uniq]$info";
+
+ if (!@rename($filedata['filename'], $new_filename)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot rename file');
+ }
+
+ $filedata['flags'] = $flags;
+ $filedata['filename'] = $new_filename;
+
+ $this->_files[$id - 1] = $filedata;
+ }
+
+
+ /**
+ * stub for not supported message deletion
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeMessage($id)
+ {
+ $filename = $this->_getFileData($id, 'filename');
+
+ if ($this->_quota) {
+ $size = filesize($filename);
+ }
+
+ if (!@unlink($filename)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot remove message');
+ }
+ unset($this->_files[$id - 1]);
+ // remove the gap
+ $this->_files = array_values($this->_files);
+ if ($this->_quota) {
+ $this->_addQuotaEntry(0 - (int)$size, -1);
+ }
+ }
+
+ /**
+ * enable/disable quota and set a quota value if wanted or needed
+ *
+ * You can enable/disable quota with true/false. If you don't have
+ * a MDA or want to enforce a quota value you can also set this value
+ * here. Use array('size' => SIZE_QUOTA, 'count' => MAX_MESSAGE) do
+ * define your quota. Order of these fields does matter!
+ *
+ * @param bool|array $value new quota value
+ * @return null
+ */
+ public function setQuota($value) {
+ $this->_quota = $value;
+ }
+
+ /**
+ * get currently set quota
+ *
+ * @see Zend_Mail_Storage_Writable_Maildir::setQuota()
+ *
+ * @return bool|array
+ */
+ public function getQuota($fromStorage = false) {
+ if ($fromStorage) {
+ $fh = @fopen($this->_rootdir . 'maildirsize', 'r');
+ if (!$fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ require_once 'Zend/Mail/Storage/Exception.php';
+ throw new Zend_Mail_Storage_Exception('cannot open maildirsize');
+ }
+ $definition = fgets($fh);
+ fclose($fh);
+ $definition = explode(',', trim($definition));
+ $quota = array();
+ foreach ($definition as $member) {
+ $key = $member[strlen($member) - 1];
+ if ($key == 'S' || $key == 'C') {
+ $key = $key == 'C' ? 'count' : 'size';
+ }
+ $quota[$key] = substr($member, 0, -1);
+ }
+ return $quota;
+ }
+
+ return $this->_quota;
+ }
+
+ /**
+ * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating maildirsize"
+ */
+ protected function _calculateMaildirsize() {
+ $timestamps = array();
+ $messages = 0;
+ $total_size = 0;
+
+ if (is_array($this->_quota)) {
+ $quota = $this->_quota;
+ } else {
+ try {
+ $quota = $this->getQuota(true);
+ } catch (Zend_Mail_Storage_Exception $e) {
+ throw new Zend_Mail_Storage_Exception('no quota defintion found');
+ }
+ }
+
+ $folders = new RecursiveIteratorIterator($this->getFolders(), RecursiveIteratorIterator::SELF_FIRST);
+ foreach ($folders as $folder) {
+ $subdir = $folder->getGlobalName();
+ if ($subdir == 'INBOX') {
+ $subdir = '';
+ } else {
+ $subdir = '.' . $subdir;
+ }
+ if ($subdir == 'Trash') {
+ continue;
+ }
+
+ foreach (array('cur', 'new') as $subsubdir) {
+ $dirname = $this->_rootdir . $subdir . DIRECTORY_SEPARATOR . $subsubdir . DIRECTORY_SEPARATOR;
+ if (!file_exists($dirname)) {
+ continue;
+ }
+ // NOTE: we are using mtime instead of "the latest timestamp". The latest would be atime
+ // and as we are accessing the directory it would make the whole calculation useless.
+ $timestamps[$dirname] = filemtime($dirname);
+
+ $dh = opendir($dirname);
+ // NOTE: Should have been checked in constructor. Not throwing an exception here, quotas will
+ // therefore not be fully enforeced, but next request will fail anyway, if problem persists.
+ if (!$dh) {
+ continue;
+ }
+
+
+ while (($entry = readdir()) !== false) {
+ if ($entry[0] == '.' || !is_file($dirname . $entry)) {
+ continue;
+ }
+
+ if (strpos($entry, ',S=')) {
+ strtok($entry, '=');
+ $filesize = strtok(':');
+ if (is_numeric($filesize)) {
+ $total_size += $filesize;
+ ++$messages;
+ continue;
+ }
+ }
+ $size = filesize($dirname . $entry);
+ if ($size === false) {
+ // ignore, as we assume file got removed
+ continue;
+ }
+ $total_size += $size;
+ ++$messages;
+ }
+ }
+ }
+
+ $tmp = $this->_createTmpFile();
+ $fh = $tmp['handle'];
+ $definition = array();
+ foreach ($quota as $type => $value) {
+ if ($type == 'size' || $type == 'count') {
+ $type = $type == 'count' ? 'C' : 'S';
+ }
+ $definition[] = $value . $type;
+ }
+ $definition = implode(',', $definition);
+ fputs($fh, "$definition\n");
+ fputs($fh, "$total_size $messages\n");
+ fclose($fh);
+ rename($tmp['filename'], $this->_rootdir . 'maildirsize');
+ foreach ($timestamps as $dir => $timestamp) {
+ if ($timestamp < filemtime($dir)) {
+ unlink($this->_rootdir . 'maildirsize');
+ break;
+ }
+ }
+
+ return array('size' => $total_size, 'count' => $messages, 'quota' => $quota);
+ }
+
+ /**
+ * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating the quota for a Maildir++"
+ */
+ protected function _calculateQuota($forceRecalc = false) {
+ $fh = null;
+ $total_size = 0;
+ $messages = 0;
+ $maildirsize = '';
+ if (!$forceRecalc && file_exists($this->_rootdir . 'maildirsize') && filesize($this->_rootdir . 'maildirsize') < 5120) {
+ $fh = fopen($this->_rootdir . 'maildirsize', 'r');
+ }
+ if ($fh) {
+ $maildirsize = fread($fh, 5120);
+ if (strlen($maildirsize) >= 5120) {
+ fclose($fh);
+ $fh = null;
+ $maildirsize = '';
+ }
+ }
+ if (!$fh) {
+ $result = $this->_calculateMaildirsize();
+ $total_size = $result['size'];
+ $messages = $result['count'];
+ $quota = $result['quota'];
+ } else {
+ $maildirsize = explode("\n", $maildirsize);
+ if (is_array($this->_quota)) {
+ $quota = $this->_quota;
+ } else {
+ $definition = explode(',', $maildirsize[0]);
+ $quota = array();
+ foreach ($definition as $member) {
+ $key = $member[strlen($member) - 1];
+ if ($key == 'S' || $key == 'C') {
+ $key = $key == 'C' ? 'count' : 'size';
+ }
+ $quota[$key] = substr($member, 0, -1);
+ }
+ }
+ unset($maildirsize[0]);
+ foreach ($maildirsize as $line) {
+ list($size, $count) = explode(' ', trim($line));
+ $total_size += $size;
+ $messages += $count;
+ }
+ }
+
+ $over_quota = false;
+ $over_quota = $over_quota || (isset($quota['size']) && $total_size > $quota['size']);
+ $over_quota = $over_quota || (isset($quota['count']) && $messages > $quota['count']);
+ // NOTE: $maildirsize equals false if it wasn't set (AKA we recalculated) or it's only
+ // one line, because $maildirsize[0] gets unsetted.
+ // Also we're using local time to calculate the 15 minute offset. Touching a file just for known the
+ // local time of the file storage isn't worth the hassle.
+ if ($over_quota && ($maildirsize || filemtime($this->_rootdir . 'maildirsize') > time() - 900)) {
+ $result = $this->_calculateMaildirsize();
+ $total_size = $result['size'];
+ $messages = $result['count'];
+ $quota = $result['quota'];
+ $over_quota = false;
+ $over_quota = $over_quota || (isset($quota['size']) && $total_size > $quota['size']);
+ $over_quota = $over_quota || (isset($quota['count']) && $messages > $quota['count']);
+ }
+
+ if ($fh) {
+ // TODO is there a safe way to keep the handle open for writing?
+ fclose($fh);
+ }
+
+ return array('size' => $total_size, 'count' => $messages, 'quota' => $quota, 'over_quota' => $over_quota);
+ }
+
+ protected function _addQuotaEntry($size, $count = 1) {
+ if (!file_exists($this->_rootdir . 'maildirsize')) {
+ // TODO: should get file handler from _calculateQuota
+ }
+ $size = (int)$size;
+ $count = (int)$count;
+ file_put_contents($this->_rootdir . 'maildirsize', "$size $count\n", FILE_APPEND);
+ }
+
+ /**
+ * check if storage is currently over quota
+ *
+ * @param bool $detailedResponse return known data of quota and current size and message count @see _calculateQuota()
+ * @return bool|array over quota state or detailed response
+ */
+ public function checkQuota($detailedResponse = false, $forceRecalc = false) {
+ $result = $this->_calculateQuota($forceRecalc);
+ return $detailedResponse ? $result : $result['over_quota'];
+ }
+}
diff --git a/lib/Zend/Mail/Transport/Abstract.php b/lib/Zend/Mail/Transport/Abstract.php
new file mode 100644
index 0000000..603411a
--- /dev/null
+++ b/lib/Zend/Mail/Transport/Abstract.php
@@ -0,0 +1,350 @@
+<?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 Transport
+ * @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 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mime
+ */
+require_once 'Zend/Mime.php';
+
+
+/**
+ * Abstract for sending eMails through different
+ * ways of transport
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Mail_Transport_Abstract
+{
+ /**
+ * Mail body
+ * @var string
+ * @access public
+ */
+ public $body = '';
+
+ /**
+ * MIME boundary
+ * @var string
+ * @access public
+ */
+ public $boundary = '';
+
+ /**
+ * Mail header string
+ * @var string
+ * @access public
+ */
+ public $header = '';
+
+ /**
+ * Array of message headers
+ * @var array
+ * @access protected
+ */
+ protected $_headers = array();
+
+ /**
+ * Message is a multipart message
+ * @var boolean
+ * @access protected
+ */
+ protected $_isMultipart = false;
+
+ /**
+ * Zend_Mail object
+ * @var false|Zend_Mail
+ * @access protected
+ */
+ protected $_mail = false;
+
+ /**
+ * Array of message parts
+ * @var array
+ * @access protected
+ */
+ protected $_parts = array();
+
+ /**
+ * Recipients string
+ * @var string
+ * @access public
+ */
+ public $recipients = '';
+
+ /**
+ * EOL character string used by transport
+ * @var string
+ * @access public
+ */
+ public $EOL = "\r\n";
+
+ /**
+ * Send an email independent from the used transport
+ *
+ * The requisite information for the email will be found in the following
+ * properties:
+ *
+ * - {@link $recipients} - list of recipients (string)
+ * - {@link $header} - message header
+ * - {@link $body} - message body
+ */
+ abstract protected function _sendMail();
+
+ /**
+ * Return all mail headers as an array
+ *
+ * If a boundary is given, a multipart header is generated with a
+ * Content-Type of either multipart/alternative or multipart/mixed depending
+ * on the mail parts present in the {@link $_mail Zend_Mail object} present.
+ *
+ * @param string $boundary
+ * @return array
+ */
+ protected function _getHeaders($boundary)
+ {
+ if (null !== $boundary) {
+ // Build multipart mail
+ $type = $this->_mail->getType();
+ if (!$type) {
+ if ($this->_mail->hasAttachments) {
+ $type = Zend_Mime::MULTIPART_MIXED;
+ } elseif ($this->_mail->getBodyText() && $this->_mail->getBodyHtml()) {
+ $type = Zend_Mime::MULTIPART_ALTERNATIVE;
+ } else {
+ $type = Zend_Mime::MULTIPART_MIXED;
+ }
+ }
+
+ $this->_headers['Content-Type'] = array(
+ $type . '; charset="' . $this->_mail->getCharset() . '";'
+ . $this->EOL
+ . " " . 'boundary="' . $boundary . '"'
+ );
+ $this->_headers['MIME-Version'] = array('1.0');
+
+ $this->boundary = $boundary;
+ }
+
+ return $this->_headers;
+ }
+
+ /**
+ * Prepend header name to header value
+ *
+ * @param string $item
+ * @param string $key
+ * @param string $prefix
+ * @static
+ * @access protected
+ * @return void
+ */
+ protected static function _formatHeader(&$item, $key, $prefix)
+ {
+ $item = $prefix . ': ' . $item;
+ }
+
+ /**
+ * Prepare header string for use in transport
+ *
+ * Prepares and generates {@link $header} based on the headers provided.
+ *
+ * @param mixed $headers
+ * @access protected
+ * @return void
+ * @throws Zend_Mail_Transport_Exception if any header lines exceed 998
+ * characters
+ */
+ protected function _prepareHeaders($headers)
+ {
+ if (!$this->_mail) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Transport_Exception('Missing Zend_Mail object in _mail property');
+ }
+
+ $this->header = '';
+
+ foreach ($headers as $header => $content) {
+ if (isset($content['append'])) {
+ unset($content['append']);
+ $value = implode(',' . $this->EOL . ' ', $content);
+ $this->header .= $header . ': ' . $value . $this->EOL;
+ } else {
+ array_walk($content, array(get_class($this), '_formatHeader'), $header);
+ $this->header .= implode($this->EOL, $content) . $this->EOL;
+ }
+ }
+
+ // Sanity check on headers -- should not be > 998 characters
+ $sane = true;
+ foreach (explode($this->EOL, $this->header) as $line) {
+ if (strlen(trim($line)) > 998) {
+ $sane = false;
+ break;
+ }
+ }
+ if (!$sane) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Exception('At least one mail header line is too long');
+ }
+ }
+
+ /**
+ * Generate MIME compliant message from the current configuration
+ *
+ * If both a text and HTML body are present, generates a
+ * multipart/alternative Zend_Mime_Part containing the headers and contents
+ * of each. Otherwise, uses whichever of the text or HTML parts present.
+ *
+ * The content part is then prepended to the list of Zend_Mime_Parts for
+ * this message.
+ *
+ * @return void
+ */
+ protected function _buildBody()
+ {
+ if (($text = $this->_mail->getBodyText())
+ && ($html = $this->_mail->getBodyHtml()))
+ {
+ // Generate unique boundary for multipart/alternative
+ $mime = new Zend_Mime(null);
+ $boundaryLine = $mime->boundaryLine($this->EOL);
+ $boundaryEnd = $mime->mimeEnd($this->EOL);
+
+ $text->disposition = false;
+ $html->disposition = false;
+
+ $body = $boundaryLine
+ . $text->getHeaders($this->EOL)
+ . $this->EOL
+ . $text->getContent($this->EOL)
+ . $this->EOL
+ . $boundaryLine
+ . $html->getHeaders($this->EOL)
+ . $this->EOL
+ . $html->getContent($this->EOL)
+ . $this->EOL
+ . $boundaryEnd;
+
+ $mp = new Zend_Mime_Part($body);
+ $mp->type = Zend_Mime::MULTIPART_ALTERNATIVE;
+ $mp->boundary = $mime->boundary();
+
+ $this->_isMultipart = true;
+
+ // Ensure first part contains text alternatives
+ array_unshift($this->_parts, $mp);
+
+ // Get headers
+ $this->_headers = $this->_mail->getHeaders();
+ return;
+ }
+
+ // If not multipart, then get the body
+ if (false !== ($body = $this->_mail->getBodyHtml())) {
+ array_unshift($this->_parts, $body);
+ } elseif (false !== ($body = $this->_mail->getBodyText())) {
+ array_unshift($this->_parts, $body);
+ }
+
+ if (!$body) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Transport_Exception('No body specified');
+ }
+
+ // Get headers
+ $this->_headers = $this->_mail->getHeaders();
+ $headers = $body->getHeadersArray($this->EOL);
+ foreach ($headers as $header) {
+ // Headers in Zend_Mime_Part are kept as arrays with two elements, a
+ // key and a value
+ $this->_headers[$header[0]] = array($header[1]);
+ }
+ }
+
+ /**
+ * Send a mail using this transport
+ *
+ * @param Zend_Mail $mail
+ * @access public
+ * @return void
+ * @throws Zend_Mail_Transport_Exception if mail is empty
+ */
+ public function send(Zend_Mail $mail)
+ {
+ $this->_isMultipart = false;
+ $this->_mail = $mail;
+ $this->_parts = $mail->getParts();
+ $mime = $mail->getMime();
+
+ // Build body content
+ $this->_buildBody();
+
+ // Determine number of parts and boundary
+ $count = count($this->_parts);
+ $boundary = null;
+ if ($count < 1) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Transport_Exception('Empty mail cannot be sent');
+ }
+
+ if ($count > 1) {
+ // Multipart message; create new MIME object and boundary
+ $mime = new Zend_Mime($this->_mail->getMimeBoundary());
+ $boundary = $mime->boundary();
+ } elseif ($this->_isMultipart) {
+ // multipart/alternative -- grab boundary
+ $boundary = $this->_parts[0]->boundary;
+ }
+
+ // Determine recipients, and prepare headers
+ $this->recipients = implode(',', $mail->getRecipients());
+ $this->_prepareHeaders($this->_getHeaders($boundary));
+
+ // Create message body
+ // This is done so that the same Zend_Mail object can be used in
+ // multiple transports
+ $message = new Zend_Mime_Message();
+ $message->setParts($this->_parts);
+ $message->setMime($mime);
+ $this->body = $message->generateMessage($this->EOL);
+
+ // Send to transport!
+ $this->_sendMail();
+ }
+}
diff --git a/lib/Zend/Mail/Transport/Exception.php b/lib/Zend/Mail/Transport/Exception.php
new file mode 100644
index 0000000..16ef240
--- /dev/null
+++ b/lib/Zend/Mail/Transport/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 Transport
+ * @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_Mail_Exception
+ */
+require_once 'Zend/Mail/Exception.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @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_Transport_Exception extends Zend_Mail_Exception
+{}
+
diff --git a/lib/Zend/Mail/Transport/Sendmail.php b/lib/Zend/Mail/Transport/Sendmail.php
new file mode 100644
index 0000000..651df3c
--- /dev/null
+++ b/lib/Zend/Mail/Transport/Sendmail.php
@@ -0,0 +1,170 @@
+<?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 Transport
+ * @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: Sendmail.php 8064 2008-02-16 10:58:39Z thomas $
+ */
+
+
+/**
+ * @see Zend_Mail_Transport_Abstract
+ */
+require_once 'Zend/Mail/Transport/Abstract.php';
+
+
+/**
+ * Class for sending eMails via the PHP internal mail() function
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @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_Transport_Sendmail extends Zend_Mail_Transport_Abstract
+{
+ /**
+ * Subject
+ * @var string
+ * @access public
+ */
+ public $subject = null;
+
+
+ /**
+ * Config options for sendmail parameters
+ *
+ * @var string
+ */
+ public $parameters;
+
+
+ /**
+ * EOL character string
+ * @var string
+ * @access public
+ */
+ public $EOL = PHP_EOL;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $parameters OPTIONAL (Default: null)
+ * @return void
+ */
+ public function __construct($parameters = null)
+ {
+ $this->parameters = $parameters;
+ }
+
+
+ /**
+ * Send mail using PHP native mail()
+ *
+ * @access public
+ * @return void
+ * @throws Zend_Mail_Transport_Exception on mail() failure
+ */
+ public function _sendMail()
+ {
+ if ($this->parameters === null) {
+ $result = mail(
+ $this->recipients,
+ $this->_mail->getSubject(),
+ $this->body,
+ $this->header);
+ } else {
+ $result = mail(
+ $this->recipients,
+ $this->_mail->getSubject(),
+ $this->body,
+ $this->header,
+ $this->parameters);
+ }
+ if (!$result) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Transport_Exception('Unable to send mail');
+ }
+ }
+
+
+ /**
+ * Format and fix headers
+ *
+ * mail() uses its $to and $subject arguments to set the To: and Subject:
+ * headers, respectively. This method strips those out as a sanity check to
+ * prevent duplicate header entries.
+ *
+ * @access protected
+ * @param array $headers
+ * @return void
+ * @throws Zend_Mail_Transport_Exception
+ */
+ protected function _prepareHeaders($headers)
+ {
+ if (!$this->_mail) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Transport_Exception('_prepareHeaders requires a registered Zend_Mail object');
+ }
+
+ // mail() uses its $to parameter to set the To: header, and the $subject
+ // parameter to set the Subject: header. We need to strip them out.
+ if (0 === strpos(PHP_OS, 'WIN')) {
+ // If the current recipients list is empty, throw an error
+ if (empty($this->recipients)) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Transport_Exception('Missing To addresses');
+ }
+ } else {
+ // All others, simply grab the recipients and unset the To: header
+ if (!isset($headers['To'])) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Transport_Exception('Missing To header');
+ }
+
+ unset($headers['To']['append']);
+ $this->recipients = implode(',', $headers['To']);
+ }
+
+ // Remove recipient header
+ unset($headers['To']);
+
+ // Remove subject header, if present
+ if (isset($headers['Subject'])) {
+ unset($headers['Subject']);
+ }
+
+ // Prepare headers
+ parent::_prepareHeaders($headers);
+ }
+
+}
+
diff --git a/lib/Zend/Mail/Transport/Smtp.php b/lib/Zend/Mail/Transport/Smtp.php
new file mode 100644
index 0000000..714266e
--- /dev/null
+++ b/lib/Zend/Mail/Transport/Smtp.php
@@ -0,0 +1,241 @@
+<?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 Transport
+ * @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 12519 2008-11-10 18:41:24Z alexander $
+ */
+
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Mime
+ */
+require_once 'Zend/Mime.php';
+
+/**
+ * @see Zend_Mail_Protocol_Smtp
+ */
+require_once 'Zend/Mail/Protocol/Smtp.php';
+
+/**
+ * @see Zend_Mail_Transport_Abstract
+ */
+require_once 'Zend/Mail/Transport/Abstract.php';
+
+
+/**
+ * SMTP connection object
+ *
+ * Loads an instance of Zend_Mail_Protocol_Smtp and forwards smtp transactions
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @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_Transport_Smtp extends Zend_Mail_Transport_Abstract
+{
+ /**
+ * EOL character string used by transport
+ * @var string
+ * @access public
+ */
+ public $EOL = "\n";
+
+ /**
+ * Remote smtp hostname or i.p.
+ *
+ * @var string
+ */
+ protected $_host;
+
+
+ /**
+ * Port number
+ *
+ * @var integer|null
+ */
+ protected $_port;
+
+
+ /**
+ * Local client hostname or i.p.
+ *
+ * @var string
+ */
+ protected $_name = 'localhost';
+
+
+ /**
+ * Authentication type OPTIONAL
+ *
+ * @var string
+ */
+ protected $_auth;
+
+
+ /**
+ * Config options for authentication
+ *
+ * @var array
+ */
+ protected $_config;
+
+
+ /**
+ * Instance of Zend_Mail_Protocol_Smtp
+ *
+ * @var Zend_Mail_Protocol_Smtp
+ */
+ protected $_connection;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host OPTIONAL (Default: 127.0.0.1)
+ * @param array|null $config OPTIONAL (Default: null)
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', Array $config = array())
+ {
+ if (isset($config['name'])) {
+ $this->_name = $config['name'];
+ }
+ if (isset($config['port'])) {
+ $this->_port = $config['port'];
+ }
+ if (isset($config['auth'])) {
+ $this->_auth = $config['auth'];
+ }
+
+ $this->_host = $host;
+ $this->_config = $config;
+ }
+
+
+ /**
+ * Class destructor to ensure all open connections are closed
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ if ($this->_connection instanceof Zend_Mail_Protocol_Smtp) {
+ try {
+ $this->_connection->quit();
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ // ignore
+ }
+ $this->_connection->disconnect();
+ }
+ }
+
+
+ /**
+ * Sets the connection protocol instance
+ *
+ * @param Zend_Mail_Protocol_Abstract $client
+ *
+ * @return void
+ */
+ public function setConnection(Zend_Mail_Protocol_Abstract $connection)
+ {
+ $this->_connection = $connection;
+ }
+
+
+ /**
+ * Gets the connection protocol instance
+ *
+ * @return Zend_Mail_Protocol|null
+ */
+ public function getConnection()
+ {
+ return $this->_connection;
+ }
+
+ /**
+ * Send an email via the SMTP connection protocol
+ *
+ * The connection via the protocol adapter is made just-in-time to allow a
+ * developer to add a custom adapter if required before mail is sent.
+ *
+ * @return void
+ */
+ public function _sendMail()
+ {
+ // If sending multiple messages per session use existing adapter
+ if (!($this->_connection instanceof Zend_Mail_Protocol_Smtp)) {
+ // Check if authentication is required and determine required class
+ $connectionClass = 'Zend_Mail_Protocol_Smtp';
+ if ($this->_auth) {
+ $connectionClass .= '_Auth_' . ucwords($this->_auth);
+ }
+ Zend_Loader::loadClass($connectionClass);
+ $this->setConnection(new $connectionClass($this->_host, $this->_port, $this->_config));
+ $this->_connection->connect();
+ $this->_connection->helo($this->_name);
+ } else {
+ // Reset connection to ensure reliable transaction
+ $this->_connection->rset();
+ }
+
+ // Set mail return path from sender email address
+ $this->_connection->mail($this->_mail->getReturnPath());
+
+ // Set recipient forward paths
+ foreach ($this->_mail->getRecipients() as $recipient) {
+ $this->_connection->rcpt($recipient);
+ }
+
+ // Issue DATA command to client
+ $this->_connection->data($this->header . Zend_Mime::LINEEND . $this->body);
+ }
+
+ /**
+ * Format and fix headers
+ *
+ * Some SMTP servers do not strip BCC headers. Most clients do it themselves as do we.
+ *
+ * @access protected
+ * @param array $headers
+ * @return void
+ * @throws Zend_Transport_Exception
+ */
+ protected function _prepareHeaders($headers)
+ {
+ if (!$this->_mail) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ require_once 'Zend/Mail/Transport/Exception.php';
+ throw new Zend_Mail_Transport_Exception('_prepareHeaders requires a registered Zend_Mail object');
+ }
+
+ unset($headers['Bcc']);
+
+ // Prepare headers
+ parent::_prepareHeaders($headers);
+ }
+}