Location: PHPKode > projects > phlyMail Lite > phlymail/shared/lib/phm_streaming_mbox.php
<?php
/**
 * This class is meant to handle writing to and reading from MBOX files as
 * specified in RFC 4155 (@see http://www.ietf.org/rfc/rfc4155.txt for details).
 *
 * To add some confusion, let me point you to:
 * http://homepages.tesco.net./~J.deBoynePollard/FGA/mail-mbox-formats.html
 *
 * This is no real driver to work on those files as a storage backend for
 * phlyMail, but more for importing from or exporting to these mbox files.
 *
 * This class is stream based, which means, that you should be easily able to
 * work well with larger mails.
 *
 * @package phlyMail Nahariya 4.0+
 * @subpackage Email handler
 * @copyright 2006-2008 phlyLabs, Berlin, http://phlylabs.de
 * @version 0.1.0
 */
class phm_streaming_mbox {
    /**
     * This public property tells you, were exactly in the mbox file we
     * currently are.
     * The property is most useful, when querying it just after a read process
     * has finished. The value can then be passed to subsequent calls to
     * read_init() for continued reading of the mbox file.
     *
     * @var int Current offset, Default: 0
     */
    public $offset = 0;

    /**
     * Used for signalling EOF (end of file) on read processes.
     * read_line() returns false, when the end of a mail has been reached. To
     * additionally tell you, whether the end of the mbox has been reached, too,
     * query this flag - on EOF this will evaluate to true.
     *
     * @var bool
     */
    public $eof = false;

    /**
     * All errors, that appeared during the lifetime of an instance of this
     * class, separated by newlines (0x0A).
     *
     * @var unknown_type
     */
    public $error = false;

    /**
     * Private variables. None of your business!
     */
    private $mode = 'idle';
    private $handle = false;
    private $lastline = '';

    /**
     * Initialises the read operation.
     *
     * @param string  Full path to the mbox file
     *[ @param int  Offset where to start reading from; Default 0]
     * @return array  Status array, containing these keys:
     *     status  bool  Whether the first line was indeed a From_ line
     *     format  string  Can currently only be "default"; in case of unrecognized formats this will hold "unknown"
     *     from  string  Holds the envelope sender, if any. Don't rely on it!
     *     received  string  Intended to be the Received: Date and Time as stated in RFC 2822. Don't rely on it!
     * @since 0.0.1
     */
    public function read_init($path, $offset = 0)
    {
        if ($this->mode != 'idle' && $this->mode != 'read'
                || !file_exists($path) || !is_readable($path)) {
            $this->error .= 'Either in wrong mode or no access to file'.LF;
            return array('status' => false, 'format' => false, 'from' => '', 'received' => '');
        }
        $this->handle = fopen($path, 'r');
        if (!$this->handle) {
            $this->error .= 'Failed to open file'.LF;
            return array('status' => false, 'format' => false, 'from' => '', 'received' => '');
        }
        if ($offset) fseek($this->handle, $offset, SEEK_SET);
        if (feof($this->handle)) {
            $this->error .= 'EOF reached'.LF;
            $this->eof = true;
            return array('status' => false, 'format' => false, 'from' => '', 'received' => '');
        }
        $line = explode(chr(0x20), fgets($this->handle, 4096), 3);
        if (feof($this->handle)) {
            $this->error .= 'EOF reached'.LF;
            $this->eof = true;
            return array('status' => false, 'format' => false, 'from' => '', 'received' => '');
        }
        if (empty($line) || $line[0] != 'From' || count($line) < 3) {
            $this->error .= 'Either you pointed me to a wrong place in the mbox or this is none'.LF;
            return array('status' => false, 'format' => 'unknown', 'from' => '', 'received' => '');
        }
        $this->mode = 'read';
        return array('status' => true, 'format' => 'default', 'from' => $line[1], 'received' => $line[2]);
    }

    public function read_line()
    {
        if ($this->mode != 'read') {
            $this->error .= 'Wrong workmode, call read_init() first'.LF;
            return false;
        }
        if (feof($this->handle)) {
            $this->eof = true;
            $this->mode = 'idle';
            fclose($this->handle);
            $this->handle = false;
            return false;
        }
        $line = fgets($this->handle, 4096);
        $check = explode(chr(0x20), $line, 3);
        if (($this->lastline == LF || $this->lastline == CRLF) && !empty($check)
                && $check[0] == 'From' && count($check) == 3) {
            $this->mode = 'idle';
            fclose($this->handle);
            $this->handle = false;
            return false;
        }
        $this->offset = ftell($this->handle);
        $this->lastline = $line;
        return $line;
    }

    /**
     * Inits an append process
     *
     * @param string $path Full path to the mbox file
     * @param string $sender Original sender of the mail
     * @param string $received Original Received: time in RFC2822 format
     * @return bool TRUE on success, FALSE on failure
     * @since 0.0.1
     */
    public function append_init($path, $sender = 'MAILER-DAEMON', $received = false)
    {
        $bp = dirname($path);
        if (($this->mode != 'idle' && $this->mode != 'write')
                || !file_exists($bp) || !is_dir($bp) || !is_writable($bp)) {
            $this->error .= 'Either in wrong mode or no access to file'.LF;
            return false;
        }
        $this->mode = 'write';
        $this->handle = fopen($path, 'a');
        if (!$this->handle) {
            $this->error .= 'Failed to open file'.LF;
            return false;
        }
        if (!$received) $received = date('r');

        fputs($this->handle, 'From '.$sender.' '.$received.LF);
        return true;
    }

    public function append_line($string)
    {
        if ($this->mode != 'write' || false === $this->handle) {
            $this->error .= 'Either in wrong mode or no access to file'.LF;
            return false;
        }
        // This is due to limitations in the format itself, unfortunately breks messages :(
        $string = preg_replace('!^From !', '>From ', $string);

        return fputs($this->handle, rtrim($string).LF);
    }

    public function append_finalize()
    {
        fputs($this->handle, LF);
        fclose($this->handle);
        $this->handle = false;
        return true;
    }

    /**
     * This method is meant to create a file name, which should work well on different platforms.
     * This is achieved by removing all characters, which could potentially lead to confusion under either
     * Windos based OSes or Linux based ones (like \ or /).
     *
     * @param string $name The base name of the MBOX file
     * @param string $suff Pass FALSE to prevent adding a file type suffix or a string you wish to get added
     *               to the basename (e.g. 'mbox' for <basename>.mbox)
     */
    public function simplify_mbox_name($name, $suff = 'mbox')
    {
        $name = preg_replace('![\x00-\x08\x0b\x0c\x0e-\x1F-\x2F\x3A-\x40\x7B-\x9F]!u', '_', trim($name));
        $name = preg_replace('!(_)+!', '_', $name);
        return ($name.(false != $suff ? '.'.$suff : ''));
    }
}

?>
Return current item: phlyMail Lite