File indexing completed on 2024-12-22 05:36:51

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Mail
0017  * @subpackage Storage
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 
0024 /**
0025  * @see Zend_Mail_Storage_Abstract
0026  */
0027 // require_once 'Zend/Mail/Storage/Abstract.php';
0028 
0029 /**
0030  * @see Zend_Mail_Message_File
0031  */
0032 // require_once 'Zend/Mail/Message/File.php';
0033 
0034 /**
0035  * @see Zend_Mail_Storage
0036  */
0037 // require_once 'Zend/Mail/Storage.php';
0038 
0039 
0040 /**
0041  * @category   Zend
0042  * @package    Zend_Mail
0043  * @subpackage Storage
0044  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0045  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0046  */
0047 class Zend_Mail_Storage_Maildir extends Zend_Mail_Storage_Abstract
0048 {
0049     /**
0050      * used message class, change it in an extened class to extend the returned message class
0051      * @var string
0052      */
0053     protected $_messageClass = 'Zend_Mail_Message_File';
0054 
0055     /**
0056      * data of found message files in maildir dir
0057      * @var array
0058      */
0059     protected $_files = array();
0060 
0061     /**
0062      * known flag chars in filenames
0063      *
0064      * This list has to be in alphabetical order for setFlags()
0065      *
0066      * @var array
0067      */
0068     protected static $_knownFlags = array('D' => Zend_Mail_Storage::FLAG_DRAFT,
0069                                           'F' => Zend_Mail_Storage::FLAG_FLAGGED,
0070                                           'P' => Zend_Mail_Storage::FLAG_PASSED,
0071                                           'R' => Zend_Mail_Storage::FLAG_ANSWERED,
0072                                           'S' => Zend_Mail_Storage::FLAG_SEEN,
0073                                           'T' => Zend_Mail_Storage::FLAG_DELETED);
0074 
0075     // TODO: getFlags($id) for fast access if headers are not needed (i.e. just setting flags)?
0076 
0077     /**
0078      * Count messages all messages in current box
0079      *
0080      * @return int number of messages
0081      * @throws Zend_Mail_Storage_Exception
0082      */
0083     public function countMessages($flags = null)
0084     {
0085         if ($flags === null) {
0086             return count($this->_files);
0087         }
0088 
0089         $count = 0;
0090         if (!is_array($flags)) {
0091             foreach ($this->_files as $file) {
0092                 if (isset($file['flaglookup'][$flags])) {
0093                     ++$count;
0094                 }
0095             }
0096             return $count;
0097         }
0098 
0099         $flags = array_flip($flags);
0100            foreach ($this->_files as $file) {
0101                foreach ($flags as $flag => $v) {
0102                    if (!isset($file['flaglookup'][$flag])) {
0103                        continue 2;
0104                    }
0105                }
0106                ++$count;
0107            }
0108            return $count;
0109     }
0110 
0111     /**
0112      * Get one or all fields from file structure. Also checks if message is valid
0113      *
0114      * @param  int         $id    message number
0115      * @param  string|null $field wanted field
0116      * @return string|array wanted field or all fields as array
0117      * @throws Zend_Mail_Storage_Exception
0118      */
0119     protected function _getFileData($id, $field = null)
0120     {
0121         if (!isset($this->_files[$id - 1])) {
0122             /**
0123              * @see Zend_Mail_Storage_Exception
0124              */
0125             // require_once 'Zend/Mail/Storage/Exception.php';
0126             throw new Zend_Mail_Storage_Exception('id does not exist');
0127         }
0128 
0129         if (!$field) {
0130             return $this->_files[$id - 1];
0131         }
0132 
0133         if (!isset($this->_files[$id - 1][$field])) {
0134             /**
0135              * @see Zend_Mail_Storage_Exception
0136              */
0137             // require_once 'Zend/Mail/Storage/Exception.php';
0138             throw new Zend_Mail_Storage_Exception('field does not exist');
0139         }
0140 
0141         return $this->_files[$id - 1][$field];
0142     }
0143 
0144     /**
0145      * Get a list of messages with number and size
0146      *
0147      * @param  int|null $id number of message or null for all messages
0148      * @return int|array size of given message of list with all messages as array(num => size)
0149      * @throws Zend_Mail_Storage_Exception
0150      */
0151     public function getSize($id = null)
0152     {
0153         if ($id !== null) {
0154             $filedata = $this->_getFileData($id);
0155             return isset($filedata['size']) ? $filedata['size'] : filesize($filedata['filename']);
0156         }
0157 
0158         $result = array();
0159         foreach ($this->_files as $num => $data) {
0160             $result[$num + 1] = isset($data['size']) ? $data['size'] : filesize($data['filename']);
0161         }
0162 
0163         return $result;
0164     }
0165 
0166 
0167 
0168     /**
0169      * Fetch a message
0170      *
0171      * @param  int $id number of message
0172      * @return Zend_Mail_Message_File
0173      * @throws Zend_Mail_Storage_Exception
0174      */
0175     public function getMessage($id)
0176     {
0177         // TODO that's ugly, would be better to let the message class decide
0178         if (strtolower($this->_messageClass) == 'zend_mail_message_file' || is_subclass_of($this->_messageClass, 'zend_mail_message_file')) {
0179             return new $this->_messageClass(array('file'  => $this->_getFileData($id, 'filename'),
0180                                                   'flags' => $this->_getFileData($id, 'flags')));
0181         }
0182 
0183         return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $this->getRawHeader($id),
0184                                               'flags'   => $this->_getFileData($id, 'flags')));
0185     }
0186 
0187     /*
0188      * Get raw header of message or part
0189      *
0190      * @param  int               $id       number of message
0191      * @param  null|array|string $part     path to part or null for messsage header
0192      * @param  int               $topLines include this many lines with header (after an empty line)
0193      * @return string raw header
0194      * @throws Zend_Mail_Storage_Exception
0195      */
0196     public function getRawHeader($id, $part = null, $topLines = 0)
0197     {
0198         if ($part !== null) {
0199             // TODO: implement
0200             /**
0201              * @see Zend_Mail_Storage_Exception
0202              */
0203             // require_once 'Zend/Mail/Storage/Exception.php';
0204             throw new Zend_Mail_Storage_Exception('not implemented');
0205         }
0206 
0207         $fh = fopen($this->_getFileData($id, 'filename'), 'r');
0208 
0209         $content = '';
0210         while (!feof($fh)) {
0211             $line = fgets($fh);
0212             if (!trim($line)) {
0213                 break;
0214             }
0215             $content .= $line;
0216         }
0217 
0218         fclose($fh);
0219         return $content;
0220     }
0221 
0222     /*
0223      * Get raw content of message or part
0224      *
0225      * @param  int               $id   number of message
0226      * @param  null|array|string $part path to part or null for messsage content
0227      * @return string raw content
0228      * @throws Zend_Mail_Storage_Exception
0229      */
0230     public function getRawContent($id, $part = null)
0231     {
0232         if ($part !== null) {
0233             // TODO: implement
0234             /**
0235              * @see Zend_Mail_Storage_Exception
0236              */
0237             // require_once 'Zend/Mail/Storage/Exception.php';
0238             throw new Zend_Mail_Storage_Exception('not implemented');
0239         }
0240 
0241         $fh = fopen($this->_getFileData($id, 'filename'), 'r');
0242 
0243         while (!feof($fh)) {
0244             $line = fgets($fh);
0245             if (!trim($line)) {
0246                 break;
0247             }
0248         }
0249 
0250         $content = stream_get_contents($fh);
0251         fclose($fh);
0252         return $content;
0253     }
0254 
0255     /**
0256      * Create instance with parameters
0257      * Supported parameters are:
0258      *   - dirname dirname of mbox file
0259      *
0260      * @param array $params mail reader specific parameters
0261      * @throws Zend_Mail_Storage_Exception
0262      */
0263     public function __construct($params)
0264     {
0265         if (is_array($params)) {
0266             $params = (object)$params;
0267         }
0268 
0269         if (!isset($params->dirname) || !is_dir($params->dirname)) {
0270             /**
0271              * @see Zend_Mail_Storage_Exception
0272              */
0273             // require_once 'Zend/Mail/Storage/Exception.php';
0274             throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
0275         }
0276 
0277         if (!$this->_isMaildir($params->dirname)) {
0278             /**
0279              * @see Zend_Mail_Storage_Exception
0280              */
0281             // require_once 'Zend/Mail/Storage/Exception.php';
0282             throw new Zend_Mail_Storage_Exception('invalid maildir given');
0283         }
0284 
0285         $this->_has['top'] = true;
0286         $this->_has['flags'] = true;
0287         $this->_openMaildir($params->dirname);
0288     }
0289 
0290     /**
0291      * check if a given dir is a valid maildir
0292      *
0293      * @param string $dirname name of dir
0294      * @return bool dir is valid maildir
0295      */
0296     protected function _isMaildir($dirname)
0297     {
0298         if (file_exists($dirname . '/new') && !is_dir($dirname . '/new')) {
0299             return false;
0300         }
0301         if (file_exists($dirname . '/tmp') && !is_dir($dirname . '/tmp')) {
0302             return false;
0303         }
0304         return is_dir($dirname . '/cur');
0305     }
0306 
0307     /**
0308      * open given dir as current maildir
0309      *
0310      * @param string $dirname name of maildir
0311      * @return null
0312      * @throws Zend_Mail_Storage_Exception
0313      */
0314     protected function _openMaildir($dirname)
0315     {
0316         if ($this->_files) {
0317             $this->close();
0318         }
0319 
0320         $dh = @opendir($dirname . '/cur/');
0321         if (!$dh) {
0322             /**
0323              * @see Zend_Mail_Storage_Exception
0324              */
0325             // require_once 'Zend/Mail/Storage/Exception.php';
0326             throw new Zend_Mail_Storage_Exception('cannot open maildir');
0327         }
0328         $this->_getMaildirFiles($dh, $dirname . '/cur/');
0329         closedir($dh);
0330 
0331         $dh = @opendir($dirname . '/new/');
0332         if ($dh) {
0333             $this->_getMaildirFiles($dh, $dirname . '/new/', array(Zend_Mail_Storage::FLAG_RECENT));
0334             closedir($dh);
0335         } else if (file_exists($dirname . '/new/')) {
0336             /**
0337              * @see Zend_Mail_Storage_Exception
0338              */
0339             // require_once 'Zend/Mail/Storage/Exception.php';
0340             throw new Zend_Mail_Storage_Exception('cannot read recent mails in maildir');
0341         }
0342     }
0343 
0344     /**
0345      * find all files in opened dir handle and add to maildir files
0346      *
0347      * @param resource $dh            dir handle used for search
0348      * @param string   $dirname       dirname of dir in $dh
0349      * @param array    $default_flags default flags for given dir
0350      * @return null
0351      */
0352     protected function _getMaildirFiles($dh, $dirname, $default_flags = array())
0353     {
0354         while (($entry = readdir($dh)) !== false) {
0355             if ($entry[0] == '.' || !is_file($dirname . $entry)) {
0356                 continue;
0357             }
0358 
0359             @list($uniq, $info) = explode(':', $entry, 2);
0360             @list(,$size) = explode(',', $uniq, 2);
0361             if ($size && $size[0] == 'S' && $size[1] == '=') {
0362                 $size = substr($size, 2);
0363             }
0364             if (!ctype_digit($size)) {
0365                 $size = null;
0366             }
0367             @list($version, $flags) = explode(',', $info, 2);
0368             if ($version != 2) {
0369                 $flags = '';
0370             }
0371 
0372             $named_flags = $default_flags;
0373             $length = strlen($flags);
0374             for ($i = 0; $i < $length; ++$i) {
0375                 $flag = $flags[$i];
0376                 $named_flags[$flag] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag;
0377             }
0378 
0379             $data = array('uniq'       => $uniq,
0380                           'flags'      => $named_flags,
0381                           'flaglookup' => array_flip($named_flags),
0382                           'filename'   => $dirname . $entry);
0383             if ($size !== null) {
0384                 $data['size'] = (int)$size;
0385             }
0386             $this->_files[] = $data;
0387         }
0388     }
0389 
0390 
0391     /**
0392      * Close resource for mail lib. If you need to control, when the resource
0393      * is closed. Otherwise the destructor would call this.
0394      *
0395      * @return void
0396      */
0397     public function close()
0398     {
0399         $this->_files = array();
0400     }
0401 
0402 
0403     /**
0404      * Waste some CPU cycles doing nothing.
0405      *
0406      * @return void
0407      */
0408     public function noop()
0409     {
0410         return true;
0411     }
0412 
0413 
0414     /**
0415      * stub for not supported message deletion
0416      *
0417      * @return null
0418      * @throws Zend_Mail_Storage_Exception
0419      */
0420     public function removeMessage($id)
0421     {
0422         /**
0423          * @see Zend_Mail_Storage_Exception
0424          */
0425         // require_once 'Zend/Mail/Storage/Exception.php';
0426         throw new Zend_Mail_Storage_Exception('maildir is (currently) read-only');
0427     }
0428 
0429     /**
0430      * get unique id for one or all messages
0431      *
0432      * if storage does not support unique ids it's the same as the message number
0433      *
0434      * @param int|null $id message number
0435      * @return array|string message number for given message or all messages as array
0436      * @throws Zend_Mail_Storage_Exception
0437      */
0438     public function getUniqueId($id = null)
0439     {
0440         if ($id) {
0441             return $this->_getFileData($id, 'uniq');
0442         }
0443 
0444         $ids = array();
0445         foreach ($this->_files as $num => $file) {
0446             $ids[$num + 1] = $file['uniq'];
0447         }
0448         return $ids;
0449     }
0450 
0451     /**
0452      * get a message number from a unique id
0453      *
0454      * I.e. if you have a webmailer that supports deleting messages you should use unique ids
0455      * as parameter and use this method to translate it to message number right before calling removeMessage()
0456      *
0457      * @param string $id unique id
0458      * @return int message number
0459      * @throws Zend_Mail_Storage_Exception
0460      */
0461     public function getNumberByUniqueId($id)
0462     {
0463         foreach ($this->_files as $num => $file) {
0464             if ($file['uniq'] == $id) {
0465                 return $num + 1;
0466             }
0467         }
0468 
0469         /**
0470          * @see Zend_Mail_Storage_Exception
0471          */
0472         // require_once 'Zend/Mail/Storage/Exception.php';
0473         throw new Zend_Mail_Storage_Exception('unique id not found');
0474     }
0475 }