<?php

require_once IMP_BASE . '/lib/constants.php';

/**
 * IMP Base Class.
 *
 * $Horde: imp/lib/IMP.php,v 1.198.2.64 2004/07/24 18:24:40 chuck Exp $
 *
 * Copyright 1999-2003 Chuck Hagenbuch <chuck@horde.org>
 * Copyright 1999-2003 Jon Parise <jon@horde.org>
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @author  Jon Parise <jon@horde.org>
 * @version $Revision: 1.198.2.64 $
 * @since   IMP 2.3.5
 * @package imp
 */
class IMP {

    /**
     * Take information posted from a login attempt and try setting up
     * an initial IMP session. Handle Horde authentication, if
     * required, and only do enough work to see if the user can log
     * in. This function should only be called once, when the user
     * first logs in.
     *
     * @access public
     *
     * @return mixed True on success, string with the failure reason on failure.
     */
    function createSession()
    {
        global $conf, $registry;

        if (!isset($_POST['imapuser'])
            || !isset($_POST['pass'])
            || !isset($_POST['server'])) {
            return 'failed';
        }

        $_SESSION['imp'] = array();
        $_SESSION['imp']['user'] = trim(Horde::getFormData('imapuser'));

        /* Define these now so they're available to the vinfo
           hooks. */
        $server = Horde::getFormData('server', '');
        $realm = Horde::getFormData('realm', '');

        /* Run the username through virtualhost expansion functions if
           necessary. */
        if (!empty($conf['hooks']['vinfo']) && function_exists($conf['hooks']['vinfo'])) {
            $_SESSION['imp']['user'] = call_user_func($conf['hooks']['vinfo']);
        }

        $_SESSION['imp']['pass'] = Secret::write(Secret::getKey('imp'), Horde::getFormData('pass'));

        /* We might need to override some of the defaults with
           environmental settings. */
        include_once IMP_BASE . '/config/servers.php';

        if (Auth::getAuth()) {
            $_SESSION['imp']['uniquser'] = Auth::getAuth();
        } elseif (($conf['server']['server_list'] != 'none') && !empty($servers[$server]['realm'])) {
            $_SESSION['imp']['uniquser'] = $_SESSION['imp']['user'] . '@' . $servers[$server]['realm'];
        } elseif (!empty($realm)) {
            $_SESSION['imp']['uniquser'] = $_SESSION['imp']['user'] . '@' . $realm;
        } else {
            $_SESSION['imp']['uniquser'] = $_SESSION['imp']['user'];
        }

        if ($conf['server']['server_list'] != 'none'
            && isset($servers[$server])
            && is_array($servers[$server])) {

            $_SESSION['imp']['server'] = $servers[$server]['server'];
            $_SESSION['imp']['port'] = $servers[$server]['port'];
            $_SESSION['imp']['protocol'] = $servers[$server]['protocol'];
            $_SESSION['imp']['maildomain'] = $servers[$server]['maildomain'];
            $_SESSION['imp']['namespace'] = $servers[$server]['namespace'];
            $_SESSION['imp']['folders'] = $servers[$server]['folders'];

            if (($conf['mailer']['type'] == 'smtp') &&
                !empty($servers[$server]['smtphost'])) {
                $_SESSION['imp']['smtphost'] = $servers[$server]['smtphost'];
            }
        } else {
            foreach ($servers as $key => $curServer) {
                if (!isset($server_key) && substr($key, 0, 1) != '_') {
                    $server_key = $key;
                }
                if (IMP::isPreferredServer($curServer, $key)) {
                    $server_key = $key;
                    break;
                }
            }
            if ($conf['server']['change_server']) {
                $_SESSION['imp']['server'] = $server;
            } else {
                $_SESSION['imp']['server'] = $servers[$server_key]['server'];
            }
            if ($conf['server']['change_port']) {
                $_SESSION['imp']['port'] = Horde::getFormData('port', '');
            } else {
                $_SESSION['imp']['port'] = $servers[$server_key]['port'];
            }
            if ($conf['server']['change_protocol']) {
                $_SESSION['imp']['protocol'] = Horde::getFormData('protocol', '');
            } else {
                $_SESSION['imp']['protocol'] = $servers[$server_key]['protocol'];
            }
            if ($conf['server']['change_folders']) {
                $_SESSION['imp']['folders'] = Horde::getFormData('folders');
            } else {
                $_SESSION['imp']['folders'] = $servers[$server_key]['folders'];
            }
            $_SESSION['imp']['maildomain'] = Horde::getFormData('maildomain', '');
            $_SESSION['imp']['namespace'] = Horde::getFormData('namespace', '');
        }

        $protocols = explode('/', $_SESSION['imp']['protocol']);
        if (is_array($protocols)) {
            $_SESSION['imp']['base_protocol'] = $protocols[0];
        } else {
            $_SESSION['imp']['base_protocol'] = $_SESSION['imp']['protocol'];
        }

        if (Horde::getFormData('url')) {
            $_SESSION['imp']['direct_access'] = false;
        } else {
            $_SESSION['imp']['direct_access'] = true;
        }

        $_SESSION['imp']['mailbox'] = '';
        if (IMP::authenticate(OP_HALFOPEN) === true) {
            if ($registry->getMethod('auth/login') == 'imp') {
                Auth::setAuth($_SESSION['imp']['uniquser'], array('password' => Horde::dispelMagicQuotes($_POST['pass'])));
                $registry->loadPrefs();
            }
            global $prefs;

            /* Set the session variables. These are cached. */
            $_SESSION['imp']['mailbox'] = $prefs->getValue('mailbox');
            IMP::setLabel($_SESSION['imp']);

            $GLOBALS['imp'] = &$_SESSION['imp'];

            return true;
        } else {
            return 'failed';
        }
    }

    /**
     * Set up the session for use in the page. Retrieve preferences,
     * and make sure to update the mailbox, the sorting information,
     * etc, if any of them have been changed. Also does
     * protocol-specific tasks, such as automatically disabling
     * folders if pop3 is being used.
     *
     * @access public
     *
     * @return mixed True on success, string containing the failure reason on failure.
     */
    function setupSession()
    {
        global $conf, $prefs, $registry;

        if (!isset($_SESSION['imp']) ||
            !is_array($_SESSION['imp']) ||
            !isset($_SESSION['imp']['user'])) {
            if (isset($prefs)) {
                $prefs->cleanup(true);
            }
            return 'session';
        } elseif (!isset($GLOBALS['imp'])) {
            $GLOBALS['imp'] = &$_SESSION['imp'];
        }

        if (isset($_GET['sortby'])) {
            $prefs->setValue('sortby', $_GET['sortby']);
        }
        if (isset($_GET['sortdir'])) {
            $prefs->setValue('sortdir', $_GET['sortdir']);
        }

        $mailbox = Horde::getFormData('mailbox');
        if ($mailbox != null) {
            $GLOBALS['imp']['mailbox'] = $mailbox;
        } elseif (!isset($GLOBALS['imp']['mailbox'])) {
            $GLOBALS['imp']['mailbox'] = 'INBOX';
        }
        IMP::setLabel($GLOBALS['imp']);

        switch ($GLOBALS['imp']['base_protocol']) {
        case 'pop3':
            $conf['user']['allow_folders'] = false;
            $prefs->setValue('save_sent_mail', false);
            $prefs->setLocked('save_sent_mail', true);
            $prefs->setLocked('sent_mail_folder', true);
            $prefs->setLocked('drafts_folder', true);
            $prefs->setLocked('trash_folder', true);
            break;
        }

        return true;
    }

    /**
     * Attempt to open a connection to the IMAP server using the
     * information in the $imp session variable.
     *
     * @access public
     *
     * @param integer $flags  (optional) Flags to pass to IMAP_OPEN. Valid values are:
     * <pre>
     *     OP_READONLY  : Open the mailbox read-only.
     *     OP_ANONYMOUS : (NNTP only) Don't use or update a .newrc file.
     *     OP_HALFOPEN  : (IMAP and NNTP only) Open a connection, but not a specific mailbox.
     *     CL_EXPUNGE   : Expunge the mailbox automatically when the stream is closed.
     * </pre>
     * @param boolean $setup (optional)  Run IMP::setupSession() first.
     *
     * @return mixed True on success, string containing the failure reason on failure.
     */
    function authenticate($flags = 0, $setup = false)
    {
        if ($setup) {
            $retval = IMP::setupSession();
            if ($retval !== true) {
                return $retval;
            }
        }

        global $conf;
        if (!(isset($_SESSION['imp']) && is_array($_SESSION['imp']))) {
            if (isset($GLOBALS['prefs'])) {
                $GLOBALS['prefs']->cleanup(true);
            }
            return 'session';
        }

        switch ($_SESSION['imp']['base_protocol']) {
        case 'pop3':
            $connstr = IMP::serverString($_SESSION['imp']['protocol']);
            $flags &= ~OP_ANONYMOUS;
            $flags &= ~OP_HALFOPEN;
            $_SESSION['imp']['thismailbox'] = '';
            break;
        default:
            $flags &= ~OP_ANONYMOUS;
            $_SESSION['imp']['thismailbox'] = Horde::getFormData('thismailbox', $_SESSION['imp']['mailbox']);
            if ($_SESSION['imp']['thismailbox'] == '**search') {
                $aindex = Horde::getFormData('array_index');
                if (($aindex !== null) && isset($_SESSION['imp']['searchfolders'])) {
                    $tmp = explode(':', $_SESSION['imp']['messagefolders']);
                    $connstr = IMP::serverString($_SESSION['imp']['protocol']) . $tmp[$aindex];
                } else {
                    $connstr = IMP::serverString($_SESSION['imp']['protocol']);
                    $flags |= OP_HALFOPEN;
                }
            } else {
                $connstr = IMP::serverString($_SESSION['imp']['protocol']) . $_SESSION['imp']['thismailbox'];
            }
            break;
        }

        if ($flags == 0) {
            $_SESSION['imp']['stream'] = @imap_open($connstr, $_SESSION['imp']['user'], Secret::read(Secret::getKey('imp'), $_SESSION['imp']['pass']));
        } else {
            $_SESSION['imp']['stream'] = @imap_open($connstr, $_SESSION['imp']['user'], Secret::read(Secret::getKey('imp'), $_SESSION['imp']['pass']), $flags);
        }

        if (!$_SESSION['imp']['stream']) {
            if (!empty($_SESSION['imp']['server']) && !empty($_SESSION['imp']['port']) &&
                !empty($_SESSION['imp']['protocol']) && !empty($_SESSION['imp']['user'])) {
                if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
                    $entry = sprintf('FAILED LOGIN %s (forwarded for [%s]) to %s:%s[%s] as %s',
                                     $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SESSION['imp']['server'], $_SESSION['imp']['port'], $_SESSION['imp']['protocol'], $_SESSION['imp']['user']);
                } else {
                    $entry = sprintf('FAILED LOGIN %s to %s:%s[%s] as %s',
                                     $_SERVER['REMOTE_ADDR'], $_SESSION['imp']['server'], $_SESSION['imp']['port'], $_SESSION['imp']['protocol'], $_SESSION['imp']['user']);
                }
                Horde::logMessage($entry, __FILE__, __LINE__, LOG_ERR);
            }

            $_SESSION['imp'] = null;
            session_unregister('imp');
            if (isset($GLOBALS['prefs'])) {
                $GLOBALS['prefs']->cleanup(true);
            }
            return 'failed';
        }
        return true;
    }

    /**
     * Make sure the user has been authenticated to view the page.
     *
     * @access public
     *
     * @param option integer $flags  Any flags to pass to imap_open().
     *                               See IMP::authenticate().
     */
    function checkAuthentication($flags = null)
    {
        if (($reason = IMP::authenticate($flags, true)) !== true) {
            if (Horde::getFormData('popup')) {
                echo '<script language="JavaScript" type="text/javascript">window.close()</script>';
            } else {
                $url = Horde::applicationUrl(IMP::logoutUrl('login.php', $reason), true);
                $url = IMP::addParameter($url, 'url=' . urlencode(Horde::selfUrl()));
                header('Location: ' . $url);
            }
            exit;
        }
    }

    function loginTasks()
    {
        global $actionID, $hordeMessageStack, $prefs;

        if ($actionID == IMP_LOGIN || !empty($_SESSION['imp']['_login'])) {
            /* Get UNIX timestamp of the last time the user logged in. */
            $last_login = $prefs->getValue('last_login');

            /* Do maintenance operations. */
            if ($prefs->getValue('do_maintenance')) {
                include_once IMP_BASE . '/lib/Maintenance/imp.php';
                $maint = new Maintenance_IMP();
                $maint->runMaintenance();
            }

            /* Display user's last login time if requested. */
            /* Optional UNIX date style: "D M j H:i:s T Y" */
            if ($prefs->getValue('show_last_login')) {
                $language = $prefs->getValue('language');
                if (empty($language)) {
                    $language = Lang::select();
                }
                Lang::setLang($language);
                Lang::setDomain('imp', IMP_BASE . '/locale', Lang::getCharset());
                if ($prefs->getValue('timezone') != '') {
                    putenv('TZ=' . $prefs->getValue('timezone'));
                }
                if (empty($last_login) || !(int)$last_login) {
                    Horde::raiseMessage(_("Last login: Never"), HORDE_MESSAGE);
                } else {
                    Horde::raiseMessage(sprintf(_("Last login: %s"), strftime('%c', $last_login)), HORDE_MESSAGE);
                }
            }

            /* If the user wants to run filters on login, make sure they get run. */
            if ($prefs->getValue('filter_on_login') && $_SESSION['imp']['mailbox'] == 'INBOX') {
                $actionID = FILTER;
            }

            /* Store the current login timestamp now. */
            $prefs->setValue('last_login', time());
            $prefs->store();

            /* Store any notification messages in a session variable so it
               will be displayed on the initial login screen. */
            $_SESSION['logintasks'] = $hordeMessageStack;

            $_SESSION['imp']['_login'] = null;
        }
    }

    /**
     * Determines if the given mail server is the "preferred" mail server for
     * this web server.  This decision is based on the global 'SERVER_NAME'
     * and 'HTTP_HOST' server variables and the contents of the 'preferred'
     * either field in the server's definition.  The 'preferred' field may
     * take a single value or an array of multiple values.
     *
     * @param $server       A complete server entry from the $servers hash.
     *
     * @return  boolean     True if this entry is "preferred".
     */
    function isPreferredServer($server, $key = null)
    {
        static $urlServer;

        if (!isset($urlServer)) $urlServer = Horde::getFormData('server');
        if (!empty($urlServer)) {
            return $key == $urlServer;
        }

        if (!empty($server['preferred'])) {
            if (is_array($server['preferred'])) {
                foreach ($server['preferred'] as $preferred) {
                    if ($preferred == $_SERVER['SERVER_NAME'] ||
                        $preferred == $_SERVER['HTTP_HOST']) {
                        return true;
                    }
                }
            } elseif ($server['preferred'] == $_SERVER['SERVER_NAME'] ||
                      $server['preferred'] == $_SERVER['HTTP_HOST']) {
                return true;
            }
        }

        return false;
    }

    /**
     * Tack on any prefixes that need to be put at the beginning of
     * the folder path.
     *
     * @return string The prefix, if any.
     */
    function preambleString()
    {
        return $GLOBALS['imp']['folders'] . $GLOBALS['imp']['namespace'];
    }

    /**
     * Tack on any prefixes that need to be put at the beginning of
     * the folder path, but don't add it to INBOX, and return the full
     * mailbox name.
     *
     * @param string $mailbox   The folder path that needs the prefixes to be prepended.
     *
     * @return string           The full folder path with prefixes if needed.
     */
    function addPreambleString($mailbox)
    {
        if (empty($mailbox) || $mailbox == 'INBOX') {
            return $mailbox;
        }
        return IMP::preambleString() . $mailbox;
    }

    /**
     * Remove any prefixes from a full folder path.
     *
     * @param string $mailbox   The folder path to strip the prefix from
     *
     * @return                  The folder path without prefixes.
     */
    function stripPreambleString($mailbox)
    {
        if (substr($mailbox, 0, strlen(IMP::preambleString())) == IMP::preambleString()) {
            $mailbox = substr($mailbox, strlen(IMP::preambleString()));
        }
        return $mailbox;
    }

    /**
     * Generate a full c-client server specification string.
     *
     * @param string $protocol (optional) Override the protocol currently being used.
     * @return string          The full spec string.
     */
    function serverString($protocol = null)
    {
        if (!isset($protocol)) {
            $protocol = $_SESSION['imp']['protocol'];
        }
        return '{' . $_SESSION['imp']['server'] . ':' . $_SESSION['imp']['port'] . '/' . $protocol . '}';
    }

    /**
     * Set the plain-text label that is displayed for the current
     * mailbox, replacing '**search' with an appropriate string and
     * removing namespace and folder prefix information from what is
     * shown to the user.
     *
     * @access private
     *
     * @param array &$imp The hash to pull mailbox information from and put
     *                    the label in.
     */
    function setLabel(&$imp)
    {
        global $conf;

        if ($imp['mailbox'] == '**search') {
            $imp['label'] = _("Search Results");
        } else {
            $imp['label'] = IMP::utf7Decode($imp['mailbox']);
            if (strcmp($imp['folders'], substr($imp['label'], 0, strlen($imp['folders']))) == 0) {
                $imp['label'] = substr($imp['label'], strlen($imp['folders']));
            }
            if (strcmp($imp['namespace'], substr($imp['label'], 0, strlen($imp['namespace']))) == 0) {
                $imp['label'] = substr($imp['label'], strlen($imp['namespace']));
            }
        }
    }

    /**
     * If there is information available to tell us about a prefix in
     * front of mailbox names that shouldn't be displayed to the user,
     * then use it to strip that prefix out.
     *
     * @param string  $folder The folder name to display.
     * @param boolean $decode Whether or not the folder needs to be IMP::utf7Decoded.
     *
     * @return string The folder, with any prefix gone.
     */
    function displayFolder($folder, $decode = true)
    {
        $prefix = IMP::serverString($_SESSION['imp']['protocol']);
        if (substr($folder, 0, strlen($prefix)) == $prefix) {
            $folder = substr($folder, strlen($prefix));
        }
        $prefix = IMP::preambleString();
        if (substr($folder, 0, strlen($prefix)) == $prefix) {
            $folder = substr($folder, strlen($prefix));
        }

        return $decode ? IMP::utf7Decode($folder) : $folder;
    }

    /**
     * Takes an address object, as returned by imap_header(), and
     * formats it as a string.
     *
     * Object Format
     * For the address: John Doe <john_doe@example.com>
     * The object fields are:
     * <pre>
     *  $object->personal = Personal name ("John Doe")
     *  $object->mailbox  = The user's mailbox ("john_doe")
     *  $object->host     = The host the mailbox is on ("example.com")
     * </pre>
     *
     * @access public
     *
     * @param Object $ob      The address object to be turned into a string
     * @param mixed $filter  (optional) A user@example.com style bare address
     *                        to ignore. Either single string or an array of
     *                        strings. If the address matches $filter,
     *                        an empty string will be returned.
     *
     * @return string $address  The formatted address (Example: John Doe
     *                          <john_doe@example.com>)
     */
    function addrObject2String($ob, $filter = '')
    {
        include_once HORDE_BASE . '/lib/MIME.php';

        /* If the personal name is set, decode it. */
        $ob->personal = isset($ob->personal) ? MIME::decode($ob->personal) : '';

        /*
         * If both the mailbox and the host are empty, return an empty
         * string.  If we just let this case fall through, the call to
         * IMP::rfc822WriteAddress will end up return just a '@', which
         * is undesirable.
         */
        if (empty($ob->mailbox) && empty($ob->host)) {
            return '';
        }

        /* Make sure these two variables have some sort of value. */
        if (!isset($ob->mailbox)) $ob->mailbox = '';
        if (!isset($ob->host)) $ob->host = '';

        if ($ob->mailbox == 'undisclosed-recipients') {
            return;
        }

        /* Filter out unwanted addresses based on the $filter string. */
        if (!is_array($filter)) {
            $filter = array($filter);
        }
        foreach ($filter as $f) {
            if (strcasecmp($f, $ob->mailbox . '@' . $ob->host) == 0) {
                return '';
            }
        }

        /* Return the trimmed, formatted email address. */
        return Mime::trimEmailAddress(IMP::rfc822WriteAddress($ob->mailbox,
                                                                $ob->host, $ob->personal));
    }

    /**
     * Takes an array of address objects, as returned by imap_headerinfo(),
     * and passes each of them through IMP::addrObject2String().
     *
     * @access public
     *
     * @param array $addresses        The array of address objects.
     * @param optional mixed $filter  A user@example.com style bare address
     *                                to ignore. If any address matches
     *                                $filter, it will not be included in the
     *                                final string.
     *
     * @return string  All of the addresses in a comma-delimited string.
     */
    function addrArray2String($addresses, $filter = '')
    {
        $addrList = array();

        if (!is_array($addresses)) {
            return '';
        }

        foreach ($addresses as $addr) {
            $val = IMP::addrObject2String($addr, $filter);
            if (!empty($val)) {
                $bareAddr = strtolower(IMP::bareAddress($val));
                if (!array_key_exists($bareAddr, $addrList)) {
                    $addrList[$bareAddr] = $val;
                }
            }
        }

        if (empty($addrList)) {
            return '';
        } else {
            return implode(', ', $addrList);
        }
    }

    /**
     * Returns the bare address.
     *
     * @access public
     *
     * @param string $address             The address string.
     * @param optional boolean $multiple  Should we return multiple results?
     *
     * @return mixed  If $multiple is false, returns the mailbox@host e-mail
     *                address. If $multiple is true, returns an array of
     *                these addresses.
     */
    function bareAddress($address, $multiple = false)
    {
        $addressList = array();

        $from = imap_rfc822_parse_adrlist($address, $_SESSION['imp']['maildomain']);
        foreach ($from as $entry) {
            if (isset($entry->mailbox)) {
                if (isset($entry->host)) {
                    $addressList[] = $entry->mailbox . '@' . $entry->host;
                } else {
                    $addressList[] = $entry->mailbox;
                }
            }
        }

        if ($multiple) {
            return $addressList;
        } else {
            return array_pop($addressList);
        }
    }

    /**
     * Return an array of folders. This is a wrapper around the
     * IMP_Folder::flist() function which reduces the number of
     * arguments needed if we can assume that IMP's full environment
     * is present.
     *
     * @access public
     *
     * @param array $filter (optional) An array of mailboxes to ignore.
     *
     * @return array $mailboxes   The array of mailboxes returned by
     *                            IMP_Folder::flist().
     */
    function flist($filter = array())
    {
        require_once IMP_BASE . '/lib/Folder.php';
        global $conf, $prefs;
        return IMP_Folder::flist($_SESSION['imp']['stream'], IMP::serverString(), $_SESSION['imp']['folders'], $prefs->getValue('subscribe'), $filter, $conf['server']['hierarchies'], $conf['server']['show_dotfiles'], $_SESSION['imp']['namespace']);
    }

    /**
     * Wrapper around IMP::flist() which generates the body of a
     * &lt;select&gt; form input from the generated folder list. The
     * &lt;select&gt; and &lt;/select&gt; tags are NOT included in the output of
     * this function.
     *
     * @access public
     *
     * @param string $heading   (optional) An optional string to use as
     *                          the label for an empty-value option at
     *                          the top * of the list. Defaults to none.
     * @param boolean $abbrev   (optional) If true, abbreviate long mailbox names
     *                          by replacing the middle of the name with
     *                          '...'. Defaults to true.
     * @param array $filter     (optional) An array of mailboxes to ignore.
     * @param string $selected  (optional) The mailbox to have selected by
     *                          default. Defaults to the first option in the
     *                          list.
     *
     * @return string $options  A string containg <option> elements for each
     *                          mailbox in the list.
     */
    function flistSelect($heading = '', $abbrev = true, $filter = array(),
                         $selected = null, $new_folder_header = false)
    {
        $mailboxes = IMP::flist($filter);
        $options = '';

        if (strlen($heading) > 0) {
            $options .= '<option value="">' . $heading . "</option>\n";
        }

        if ($new_folder_header) {
            $options .= '<option value="">----</option>' . "\n";
            $options .= '<option value="*new*">' . _("New Folder") . "</option>\n";
            $options .= '<option value="">----</option>' . "\n";
        }

        foreach ($mailboxes as $mbox) {
            $sel = ($mbox['val'] && $mbox['val'] === $selected) ? ' selected="selected"' : '';
            $val = htmlspecialchars($mbox['val']);
            if ($abbrev) {
                $label = $mbox['abbrev'];
            } else {
                $label = $mbox['label'];
            }
            $options .= sprintf('<option value="%s"%s>%s</option>%s', $val, $sel, str_replace(' ', '&nbsp;', htmlspecialchars($label)), "\n");
        }

        return $options;
    }

    /**
     * Return an array of unsubscribed folders. This is a wrapper
     * around the IMP_Folder::flistUnsubscribed() function which
     * reduces the number of arguments needed if we can assume that
     * IMP's full environment is present.
     *
     * @access public
     *
     * @param array $filter  (optional) An array of mailboxes to ignore.
     *
     * @return array $mailboxes  The array of mailboxes returned by
     *                           IMP_Folder::flistUnsubscribed().
     */
    function flistUnsubscribed($filter = array())
    {
        require_once IMP_BASE . '/lib/Folder.php';
        global $conf;
        return IMP_Folder::flistUnsubscribed($_SESSION['imp']['stream'], IMP::serverString(), $_SESSION['imp']['folders'], $filter, $conf['server']['hierarchies'], $conf['server']['show_dotfiles'], $_SESSION['imp']['namespace']);
    }

    function getThisMailbox()
    {
        global $array_index;

        if ($_SESSION['imp']['mailbox'] == '**search' && isset($array_index) &&
            array_key_exists('messagefolders', $_SESSION['imp'])) {
            $tmp = explode(':', $_SESSION['imp']['messagefolders']);
            if (array_key_exists($array_index, $tmp)) {
                return $tmp[$array_index];
            }
        }

        return $_SESSION['imp']['mailbox'];
    }

    /**
     * Check for To:, Subject:, Cc:, and other compose window
     * arguments and pass back either a URI fragment or an associative
     * array with any of them which are present.
     *
     * @access public
     * @param  string $format Either 'uri' or 'array'.
     * @return string  A URI fragment or an associative array
     *                 array with any compose arguments present.
     */
    function getComposeArgs($format = 'uri')
    {
        $args = array();
        $fields = array('to', 'cc', 'bcc', 'msg', 'subject');

        foreach ($fields as $val) {
            if (($$val = Horde::getFormData($val))) {
                $args[$val] = $$val;
            }
        }

        /* decode mailto: url */
        if (array_key_exists('to', $args) && strpos($args['to'], 'mailto:') === 0) {
            $mailto = @parse_url($args['to']);
            if (is_array($mailto)) {
                $args['to'] = $mailto['path'];
                parse_str($mailto['query'], $vals);
                foreach ($fields as $val) {
                    if (array_key_exists($val, $vals)) {
                        $args[$val] = $vals[$val];
                    }
                }
            }
        }

        if (stristr($format, 'array')) {
            return $args;
        } else {
            $url = '';
            foreach ($args as $key => $val) {
                $url = IMP::addParameter($url, $key . '=' . urlencode($val));
            }
            return $url;
        }
    }

    /**
     * Returns the appropriate link to call the message composition screen.
     *
     * @access public
     *
     * @param mixed $args    (optional) Hash of arguments to pass to compose.php.
     *                       If this is passed in as a string, it will be
     *                       parsed as a toaddress?subject=foo&cc=ccaddress
     *                       (mailto-style) string.
     * @param array $extra   (optional) Hash of extra, non-standard arguments to
     *                       pass to compose.php.
     *
     * @return string $link  The link to the message composition screen.
     */
    function composeLink($args = array(), $extra = array())
    {
        global $prefs, $registry, $browser;

        /* Make sure the compose window always knows which mailbox
           it's in, for replying, forwarding, marking as answered,
           etc. */
        $extra['thismailbox'] = IMP::getThisMailbox();

        if (is_string($args)) {
            $string = $args;
            $args = array();
            if (($pos = strpos($string, '?')) !== false) {
                parse_str(substr($string, $pos + 1), $args);
                $args['to'] = substr($string, 0, $pos);
            } else {
                $args['to'] = $string;
            }
        }

        if ($prefs->getValue('compose_popup')) {
            /* %23 and %26 (for IE) and %27 need to be escaped or else
               IE's jscript will interpret it as a single quote, pound
               sign, or ampersand and refuse to work. */
            if ($browser->isBrowser('msie') || ($browser->isBrowser('mozilla') && $browser->getMajor() >= 5)) {
                $from = array('%23', '%26', '%2B');
                $to = array(urlencode('%23'), urlencode('%26'), urlencode('%2B'));
            }
            $from[] = '%27';
            $to[] = '\%27';
            return str_replace($from, $to, $registry->link('mail/compose', $args, $extra));
        } else {
            /* Merge the two argument arrays. */
            $args = array_merge($args, $extra);

            /* Convert the $args hash into proper URL parameters. */
            $params = '?';
            foreach ($args as $key => $val) {
                if (is_int($val) || !empty($val)) {
                    $key = urlencode($key);
                    $val = urlencode($val);
                    $params .= "$key=$val&";
                }
            }

            /* Check for page or message number information to let the
               compose window return to the right place in the
               mailbox. */
            global $msgindex, $page;
            if (isset($msgindex)) {
                $params .= 'start=' . urlencode(($msgindex)) . '&';
            }
            if (isset($page)) {
                $params .= 'page=' . urlencode($page) . '&';
            }

            return Horde::url('compose.php' . substr($params, 0, -1));
        }
    }

    /**
     * Generate an URL to the logout screen that includes any known
     * information, such as username, server, etc., that can be filled
     * in on the login form.
     *
     * @param string $uri               The page that will process the logout.
     * @param string $reason (optional) The reason for the logout.
     *
     * @return string     The $uri with parameters added.
     */
    function logoutUrl($uri, $reason = null)
    {
        global $conf;

        if (!strstr($uri, '?')) {
            $uri .= '?1=1';
        }

        if (!empty($_SESSION['imp']['user'])) {
            $uri .= '&imapuser=' . urlencode($_SESSION['imp']['user']);
        } elseif (Horde::getFormData('imapuser')) {
            $uri .= '&imapuser=' . urlencode(Horde::getFormData('imapuser'));
        }

        if (Horde::getFormData('server')) {
            $uri .= '&server=' . urlencode(Horde::getFormData('server'));
        } elseif (!empty($_SESSION['imp']['server'])) {
            $uri .= '&server=' . urlencode($_SESSION['imp']['server']);
        }

        if (!empty($_SESSION['imp']['port'])) {
            $uri .= '&port=' . urlencode($_SESSION['imp']['port']);
        }
        if (!empty($_SESSION['imp']['protocol'])) {
            $uri .= '&protocol=' . urlencode($_SESSION['imp']['protocol']);
        }
        if (!empty($_SESSION['imp']['folders'])) {
            $uri .= '&folders=' . urlencode($_SESSION['imp']['folders']);
        }
        if (!empty($_SESSION['imp']['language'])) {
            $uri .= '&language=' . urlencode($_SESSION['imp']['language']);
        }
        if (!empty($reason)) {
            $uri .= '&reason=' . urlencode($reason);
        }

        return $uri;
    }

    /**
     * Fetch a part of a MIME message.
     *
     * @param object MIME_Part $mime The MIME_Part object describing the part to fetch.
     *
     * @return string The raw MIME part asked for.
     */
    function getPart($mime)
    {
        return imap_fetchbody($_SESSION['imp']['stream'], $mime->index, $mime->imap_id, FT_UID);
    }

    /**
     * Fetch part of a MIME message and decode it, if it is base_64 or
     * qprint encoded.
     *
     * @param object MIME_Part $mime The MIME_Part object describing the part to fetch.
     *
     * @return string The raw MIME part asked for.
     */
    function getDecodedPart($mime)
    {
        global $registry;

        if ($mime->encoding == ENCBASE64) {
            $msg = imap_base64(imap_fetchbody($_SESSION['imp']['stream'], $mime->index, $mime->imap_id, FT_UID));
        } elseif ($mime->encoding == ENCQUOTEDPRINTABLE) {
            $raw = imap_fetchbody($_SESSION['imp']['stream'], $mime->index, $mime->imap_id, FT_UID);
            $data = imap_qprint($raw);
            if (empty($data)) {
                $data = $raw;
            }
            $msg = $data;
        } else {
            $msg = imap_fetchbody($_SESSION['imp']['stream'], $mime->index, $mime->imap_id, FT_UID);
        }

        /* Convert Cyrillic character sets. */
        $charset = Horde::getFormData('charset');
        if (stristr($registry->getCharset(), 'windows-1251')) {
            if ($charset == 'koi') {
                $msg = convert_cyr_string($msg, 'k', 'w');
            } else if ($charset == 'iso') {
                $msg = convert_cyr_string($msg, 'i', 'w');
            } else if ($charset == 'mac') {
                $msg = convert_cyr_string($msg, 'm', 'w');
            } else if ($charset == 'dos') {
                $msg = convert_cyr_string($msg, 'a', 'w');
            } else if (stristr($mime->charset, 'koi8-r')) {
                $msg = convert_cyr_string($msg, 'k', 'w');
            }
        }
        if (stristr($registry->getCharset(), 'koi8-r')) {
            if ($charset == 'win') {
                $msg = convert_cyr_string($msg, 'w', 'k');
            } else if ($charset == 'iso') {
                $msg = convert_cyr_string($msg, 'i', 'k');
            } else if ($charset == 'mac') {
                $msg = convert_cyr_string($msg, 'm', 'k');
            } else if ($charset == 'dos') {
                $msg = convert_cyr_string($msg, 'a', 'k');
            } else if (stristr($mime->charset, 'windows-1251')) {
                $msg = convert_cyr_string($msg, 'w', 'k');
            }
        }

        return $msg;
    }

    /**
     * Accepts an IMP mime variable and attached a MIME_Part and a MIME_Viewer
     * object onto it.  The actual contents of the part are not filled in here.
     * @param mime Reference to the IMP $mime variable
     */
    function resolveMimeViewer(&$mime)
    {
        global $mime_drivers, $mime_drivers_map;
        if (!isset($mime->part) || isset($mime->viewer)) {
            /* Spawn the MIME_Viewer driver */
            $mime->part = new MIME_Part( $mime->TYPE . '/' . $mime->subtype );
            $mime->viewer = &MIME_Viewer::factory($mime->part);
        }
    }

    /**
     * Returns an html table row summarizing a part of a MIME message.
     *
     * @access public
     * @param array $mime    The MIME object to summarize.
     * @param int   $index   The index of the message these parts belong to.
     *
     * @return string $row   The html table row summary.
     */
    function partSummary(&$mime, $index)
    {
        if (!($mime->size > 0)) {
            // don't display zero-size attachments
            return '';
        }

        global $mime_drivers, $mime_mapping, $conf;

        include_once HORDE_BASE . '/lib/MIME.php';
        include_once HORDE_BASE . '/lib/SessionCache.php';

        IMP::resolveMimeViewer($mime);

        $mime_type = $mime->TYPE . '/' . $mime->subtype;
        $icon = MIME_Viewer::getIcon($mime_type);

        // icon column
        $row = '<tr valign="middle"><td><img src="' . $icon . '" height="16" width="16" border="0" alt="" /></td>';

        // number column
        $row .= '<td>' . (!empty($mime->imap_id) ? htmlspecialchars($mime->imap_id) : '&nbsp;') . '</td>';

        // name/text part column
        $row .= '<td>';

        if (isset($mime->viewer->conf['no_view']) && $mime->viewer->conf['no_view']) {
            $row .= htmlspecialchars(MIME::decode($mime->description));
        } else {
            $row .= Horde::link('', sprintf(_("View %s"), MIME::decode($mime->description)), null, null, "view('" . VIEW_ATTACH . "', '" . $mime->imap_id . "', '" . SessionCache::putObject($mime) . "'); return false;") . htmlspecialchars(MIME::decode($mime->description)) . '</a>';
            $row .= '</td>';
        }

        $row .= '<td>' . htmlspecialchars($mime_type) . '</td>';
        $row .= '<td>' . htmlspecialchars($mime->size) . ' KB</td>';

        // download column
        if (!empty($mime->viewer->conf['no_download'])) {
            $row .= '<td>&nbsp;</td>';
        } else {
            require_once IMP_BASE . '/lib/Browser.php';
            $params = array();
            $params['thismailbox'] = urlencode(IMP::getThisMailbox());
            $params['index'] = $index;
            $params['id'] = $mime->imap_id;
            $params['actionID'] = DOWNLOAD_ATTACH;
            $params['mime'] = SessionCache::putObject($mime);
            $row .= '<td>';
            $row .= Horde::link(IMP_Browser::downloadUrl(MIME::decode($mime->name), $params), sprintf(_("Download %s"), MIME::decode($mime->description)));
            $row .= Horde::img('download.gif', 'alt="' . _("Download") . '"');
            $row .= '</a></td>';
        }
        $row .= "</tr>\n";
        return $row;
    }

    /**
     * Add any site-specific headers defined in config/header.txt to
     * an array of headers.
     *
     * @param array &$headers The array to add headers to.
     */
    function addSiteHeaders(&$headers)
    {
        global $conf;

        // Tack on any site-specific headers.
        if ($conf['msg']['prepend_header'] && @is_readable(IMP_BASE . '/config/header.txt')) {
            include_once HORDE_BASE . '/lib/Text.php';

            $lines = @file(IMP_BASE . '/config/header.txt');
            foreach ($lines as $line) {
                $line = Text::expandEnvironment($line);
                if (!empty($line)) {
                    list($key, $val) = explode(':', $line, 2);
                    $val = strtr($val, ':', '_');
                    $headers[trim($key)] = trim($val);
                }
            }
        }

        return $headers;
    }

    /**
     * Prepares the parameter array for the mailer connection based on the
     * current runtime environment.
     *
     * @return array    Mailer connection parameters
     */
    function prepareMailerParams()
    {
        global $conf;

        /*
         * We don't actually want to alter the contents of the $conf['mailer']
         * array, so we make a copy of the current settings.  We will apply
         * our modifications (if any) to the copy, instead.
         */
        $params = $conf['mailer']['params'];

        /*
         * Force the SMTP host value to the current SMTP server if one has been
         * selected for this connection.
         */
         if (!empty($_SESSION['imp']['smtphost'])) {
             $params['host'] = $_SESSION['imp']['smtphost'];
         }

        /*
         * If SMTP authentication has been requested, populate the username and
         * password fields based on the current values for the user.
         *
         * Note that we assume that the username and password values from the
         * current IMAP / POP3 connection are valid for SMTP authentication as
         * well.
         */
        if (!empty($params['auth'])) {
            $params['username'] = $_SESSION['imp']['user'];
            $params['password'] = Secret::read(Secret::getKey('imp'), $_SESSION['imp']['pass']);
        }

        return $params;
    }

    /**
     * Add a name=value pair to an URL, taking care of whether there
     * are existing parameters and whether to use ? or & as the glue.
     *
     * @access public
     *
     * @param string $url       The URL to modify
     * @param string $parameter The name=value pair to add.
     *
     * @return string The modified URL.
     *
     * @since IMP 3.2
     */
    function addParameter($url, $parameter)
    {
        if (!empty($parameter) && strstr($url, $parameter) === false) {
            if (substr($parameter, 0, 1) == '?') {
                $parameter = substr($parameter, 1);
            }

            $pos = strpos($url, '?');
            if ($pos !== false) {
                $url = substr_replace($url, $parameter . ini_get('arg_separator.output'),
                                      $pos + 1, 0);
            } else {
                $url .= '?' . $parameter;
            }
        }
        return $url;
    }

    /**
     * Perform filtering on the current mailbox.
     *
     * @access public
     */
    function filterMailbox()
    {
        global $prefs;

        $filters = @unserialize($prefs->getValue('filters'));
        if (is_array($filters) && count($filters) &&
            !strstr($_SESSION['imp']['protocol'], 'pop3')) {
            include_once IMP_BASE . '/lib/Folder.php';
            $baseQuery = 'UNDELETED ';
            $applied = array();
            for ($i = 0; $i < count($filters); $i++) {
                if (!isset($filters[$i])) {
                    break;
                }
                for ($j = 0; $j < count($filters[$i]['fields']); $j++) {
                    $query = $baseQuery . strtoupper($filters[$i]['fields'][$j]) . ' "' . $filters[$i]['text'] . '"';
                    $indices = @imap_search($_SESSION['imp']['stream'], $query, SE_UID);
                    if (isset($indices) && is_array($indices)) {
                        $indices = array_diff($indices, $applied);
                        if (count($indices)) {
                            if ($filters[$i]['action'] == 'delete') {
                                if ($prefs->getValue('show_filter_msg')) {
                                    $overview = @imap_fetch_overview($_SESSION['imp']['stream'], implode(',', $indices), FT_UID);
                                    foreach ($overview as $message) {
                                        if ($prefs->getValue('use_trash')) {
                                            Horde::raiseMessage(sprintf(_("Filter Activity: The message \"%s\" from \"%s\" has been moved to your Trash folder."), isset($message->subject) ? MIME::decode($message->subject) : _("[No Subject]"), MIME::decode($message->from)), HORDE_MESSAGE);
                                        } else {
                                            Horde::raiseMessage(sprintf(_("Filter Activity: The message \"%s\" from \"%s\" has been deleted."), isset($message->subject) ? MIME::decode($message->subject) : _("[No Subject]"), MIME::decode($message->from)), HORDE_MESSAGE);
                                        }
                                    }
                                }
                                IMP_Message::delete($indices);
                                $applied = array_merge($applied, $indices);
                            } elseif ($filters[$i]['folder'] != $_SESSION['imp']['mailbox']) {
                                if (IMP_Folder::exists($_SESSION['imp']['stream'], $filters[$i]['folder'])) {
                                    if ($prefs->getValue('show_filter_msg')) {
                                        $overview = @imap_fetch_overview($_SESSION['imp']['stream'], implode(',', $indices), FT_UID);
                                        foreach ($overview as $message) {
                                            Horde::raiseMessage(sprintf(_("Filter Activity: The message \"%s\" from \"%s\" has been moved to the folder \"%s\"."), isset($message->subject) ? MIME::decode($message->subject) : _("[No Subject]"), MIME::decode($message->from), $filters[$i]['folder']), HORDE_MESSAGE);
                                        }
                                    }
                                    IMP_Message::copy($filters[$i]['folder'], $indices, MOVE_MESSAGES);
                                    $applied = array_merge($applied, $indices);
                                } else {
                                    Horde::raiseMessage(sprintf(_("Problem with filter rule %d: folder does not exist: %s"), $i + 1, IMP::displayFolder($filters[$i]['folder'])), HORDE_WARNING);
                                }
                            } else {
                                $applied = array_merge($applied, $indices);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Converts a string to the modified UTF-7 charset as per
     * RFC 2060.
     *
     * @access public
     *
     * @param string $input  The string to be converted.
     *
     * @return string        The converted string.
     *
     * @since IMP 3.2
     */
    function utf7Encode($input)
    {
        global $nls;

        if (!empty($nls['charsets'][$GLOBALS['language']])) {
            $charset = $nls['charsets'][$GLOBALS['language']];
        } else {
            $charset = $nls['defaults']['charset'];
        }
        $output = false;

        /* First try iconv with transliteration. */
        if (extension_loaded('iconv')) {
            ini_set('track_errors', 1);
            $output = @iconv($charset, 'UTF7-IMAP', $input . 'a');
            if (isset($php_errormsg)) {
                $output = false;
            } else {
                $output = substr($output, 0, -1);
            }
            ini_restore('track_errors');
        }

        /* Next try mbstring. */
        if (!$output && extension_loaded('mbstring')) {
            $output = @mb_convert_encoding($input, 'UTF7-IMAP', $charset);
        }

        /* At last try imap_utf7_encode. */
        if (!$output) {
            return @imap_utf7_encode($input);
        }

        if (!$output) {
            $output = $input;
        }
        return $output;
    }

    /**
     * Converts a string from the modified UTF-7 charset as per
     * RFC 2060.
     *
     * @access public
     *
     * @param string $input  The string to be converted.
     *
     * @return string        The converted string.
     *
     * @since IMP 3.2
     */
    function utf7Decode($input)
    {
        global $nls;

        if (!empty($nls['charsets'][$GLOBALS['language']])) {
            $charset = $nls['charsets'][$GLOBALS['language']];
        } else {
            $charset = $nls['defaults']['charset'];
        }
        $output = false;

        /* First try iconv with transliteration. */
        if (extension_loaded('iconv')) {
            ini_set('track_errors', 1);
            $output = @iconv('UTF7-IMAP', $charset, $input);
            if (isset($php_errormsg)) {
                $output = false;
            }
            ini_restore('track_errors');
        }

        /* Next try mbstring. */
        if (!$output && extension_loaded('mbstring')) {
            $output = @mb_convert_encoding($input, $charset, 'UTF7-IMAP');
        }

        /* At last try imap_utf7_[en|de]code. */
        if (!$output) {
            return @imap_utf7_decode($input);
        }

        if (!$output) {
            $output = $input;
        }
        return $output;
    }

    /**
     * Quotes and escapes the given string if necessary.
     *
     * @access private
     *
     * @param string $str              The string to be quoted and escaped.
     * @param optional string $filter  A list of characters that make it
     *                                 necessary to quote the string if they
     *                                 occur.
     *
     * @return string  The correctly quoted and escaped string.
     *
     * @since IMP 3.2.2
     */
    function _rfc822Encode($str, $filter = '')
    {
        // Strip double quotes if they are around the string already.
        if (substr($str, 0, 1) == '"' && substr($str, -1) == '"') {
            $str = substr($str, 1, -1);
        }

        $filter .= "()<>@,;:\\\"[]\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";

        if (strcspn($str, $filter) != strlen($str)) {
            return '"' . str_replace('"', '\\"', str_replace('\\', '\\\\', $str)) . '"';
        } else {
            return $str;
        }
    }

    /**
     * Builds an RFC 822 compliant email address.
     *
     * @access public
     *
     * @param string $mailbox            Mailbox name.
     * @param optional string $host      Domain name of mailbox's host.
     * @param optional string $personal  Personal name phrase.
     *
     * @return string  The correctly escaped and quoted
     *                 "$personal <$mailbox@$host>" string.
     *
     * @since IMP 3.2.2
     */
    function rfc822WriteAddress($mailbox, $host = null, $personal = '')
    {
        $address = '';

        if (!empty($personal)) {
            $address .= IMP::_rfc822Encode($personal, '.');
            $address .= ' <';
        }

        if (!is_null($host)) {
            $address .= IMP::_rfc822Encode($mailbox);
            if (substr($host, 0, 1) != '@') {
                $address .= '@' . $host;
            }
        }

        if (!empty($personal)) {
            $address .= '>';
        }

        return $address;
    }

}
