alecpl
2010-08-12 1a2f8375ded7563964ea24c44c7874a92e6f7b77
program/include/rcube_imap.php
@@ -297,7 +297,9 @@
    {
        if (is_array($str) && $msgs == null)
            list($str, $msgs, $charset, $sort_field, $threads) = $str;
        if ($msgs != null && !is_array($msgs))
        if ($msgs === false)
            $msgs = array();
        else if ($msgs != null && !is_array($msgs))
            $msgs = explode(',', $msgs);
        $this->search_string     = $str;
@@ -472,14 +474,9 @@
            // get message count and store in cache
            if ($mode == 'UNSEEN')
                $search_str .= " UNSEEN";
            // get message count using SEARCH
            // not very performant but more precise (using UNDELETED)
            // disable THREADS for this request
            $threads = $this->threading;
            $this->threading = false;
            $index = $this->_search_index($mailbox, $search_str);
            $this->threading = $threads;
            $index = $this->conn->search($mailbox, $search_str);
            $count = is_array($index) ? count($index) : 0;
@@ -608,7 +605,7 @@
            else
                $msg_index = array();
            if ($slice)
            if ($slice && $msg_index)
                $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice);
            // fetch reqested headers from server
@@ -823,8 +820,11 @@
            return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice);
        // search set is threaded, we need a new one
        if ($this->search_threads)
        if ($this->search_threads) {
            if (empty($this->search_set['tree']))
                return array();
            $this->search('', $this->search_string, $this->search_charset, $sort_field);
        }
        $msgs = $this->search_set;
        $a_msg_headers = array();
@@ -946,8 +946,15 @@
    private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
    {
        // update search_set if previous data was fetched with disabled threading
        if (!$this->search_threads)
        if (!$this->search_threads) {
            if (empty($this->search_set))
                return array();
            $this->search('', $this->search_string, $this->search_charset, $sort_field);
        }
        // empty result
        if (empty($this->search_set['tree']))
            return array();
        $thread_tree = $this->search_set['tree'];
        $msg_depth = $this->search_set['depth'];
@@ -1188,20 +1195,20 @@
                $a_index = range(1, $max);
            }
            if ($this->sort_order == 'DESC')
            if ($a_index !== false && $this->sort_order == 'DESC')
                $a_index = array_reverse($a_index);
            $this->cache[$key] = $a_index;
        }
        // fetch complete message index
        else if ($this->get_capability('SORT')) {
            if ($a_index = $this->conn->sort($mailbox,
                $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) {
                if ($this->sort_order == 'DESC')
                    $a_index = array_reverse($a_index);
            $a_index = $this->conn->sort($mailbox,
                $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '');
                $this->cache[$key] = $a_index;
           }
            if ($a_index !== false && $this->sort_order == 'DESC')
                $a_index = array_reverse($a_index);
            $this->cache[$key] = $a_index;
        }
        else if ($a_index = $this->conn->fetchHeaderIndex(
            $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) {
@@ -1213,7 +1220,7 @@
            $this->cache[$key] = array_keys($a_index);
        }
        return $this->cache[$key];
        return $this->cache[$key] !== false ? $this->cache[$key] : array();
    }
@@ -1288,8 +1295,10 @@
        $all_ids = array();
        foreach($msg_index as $root) {
            $all_ids[] = $root;
            if (!empty($thread_tree[$root]))
                $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root]));
            if (!empty($thread_tree[$root])) {
                foreach (array_keys_recursive($thread_tree[$root]) as $val)
                    $all_ids[] = $val;
            }
        }
        return $all_ids;
@@ -1370,32 +1379,6 @@
        $results = $this->_search_index($mailbox, $str, $charset, $sort_field);
        // try search with US-ASCII charset (should be supported by server)
        // only if UTF-8 search is not supported
        if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII')
        {
            // convert strings to US_ASCII
            if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
                $last = 0; $res = '';
                foreach($matches[1] as $m)
                {
                    $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
                    $string = substr($str, $string_offset - 1, $m[0]);
                    $string = rcube_charset_convert($string, $charset, 'US-ASCII');
                    if (!$string)
                        continue;
                    $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string);
                    $last = $m[0] + $string_offset - 1;
                }
                if ($last < strlen($str))
                    $res .= substr($str, $last, strlen($str)-$last);
            }
            else // strings for conversion not found
                $res = $str;
            $results = $this->search($mbox_name, $res, NULL, $sort_field);
        }
        $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading);
        return $results;
@@ -1417,21 +1400,32 @@
            $criteria = 'UNDELETED '.$criteria;
        if ($this->threading) {
            list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread(
                $mailbox, $this->threading, $criteria, $charset);
            $a_messages = $this->conn->thread($mailbox, $this->threading, $criteria, $charset);
            $a_messages = array(
                'tree'    => $thread_tree,
               'depth'   => $msg_depth,
               'children' => $has_children
            );
            // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
            // but I've seen that Courier doesn't support UTF-8)
            if ($a_messages === false && $charset && $charset != 'US-ASCII')
                $a_messages = $this->conn->thread($mailbox, $this->threading,
                    $this->convert_criteria($criteria, $charset), 'US-ASCII');
            if ($a_messages !== false) {
                list ($thread_tree, $msg_depth, $has_children) = $a_messages;
                $a_messages = array(
                    'tree'    => $thread_tree,
                   'depth'   => $msg_depth,
                   'children' => $has_children
                );
            }
        }
        else if ($sort_field && $this->get_capability('SORT')) {
            $charset = $charset ? $charset : $this->default_charset;
            $a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset);
            if (!$a_messages)
               return array();
            // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
            // but I've seen that Courier doesn't support UTF-8)
            if ($a_messages === false && $charset && $charset != 'US-ASCII')
                $a_messages = $this->conn->sort($mailbox, $sort_field,
                    $this->convert_criteria($criteria, $charset), false, 'US-ASCII');
        }
        else {
            if ($orig_criteria == 'ALL') {
@@ -1440,14 +1434,16 @@
            }
            else {
                $a_messages = $this->conn->search($mailbox,
                        ($charset ? "CHARSET $charset " : '') . $criteria);
                    ($charset ? "CHARSET $charset " : '') . $criteria);
           if (!$a_messages)
               return array();
                // Error, try with US-ASCII (some servers may support only US-ASCII)
                if ($a_messages === false && $charset && $charset != 'US-ASCII')
                    $a_messages = $this->conn->search($mailbox,
                        'CHARSET US-ASCII ' . $this->convert_criteria($criteria, $charset));
            // I didn't found that SEARCH always returns sorted IDs
            if (!$this->sort_field)
                sort($a_messages);
                // I didn't found that SEARCH should return sorted IDs
               if (is_array($a_messages) && !$this->sort_field)
                    sort($a_messages);
            }
        }
@@ -1478,6 +1474,39 @@
        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
        return $this->conn->search($mailbox, $str, $ret_uid);
    }
    /**
     * Converts charset of search criteria string
     *
     * @param  string  Search string
     * @param  string  Original charset
     * @param  string  Destination charset (default US-ASCII)
     * @return string  Search string
     * @access private
     */
    private function convert_criteria($str, $charset, $dest_charset='US-ASCII')
    {
        // convert strings to US_ASCII
        if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
            $last = 0; $res = '';
            foreach ($matches[1] as $m) {
                $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
                $string = substr($str, $string_offset - 1, $m[0]);
                $string = rcube_charset_convert($string, $charset, $dest_charset);
                if (!$string)
                    continue;
                $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string);
                $last = $m[0] + $string_offset - 1;
            }
            if ($last < strlen($str))
                $res .= substr($str, $last, strlen($str)-$last);
        }
        else // strings for conversion not found
            $res = $str;
        return $res;
    }
@@ -1675,14 +1704,22 @@
        else
            $this->struct_charset = $this->_structure_charset($structure);
        $headers->ctype = strtolower($headers->ctype);
        // Here we can recognize malformed BODYSTRUCTURE and
        // 1. [@TODO] parse the message in other way to create our own message structure
        // 2. or just show the raw message body.
        // Example of structure for malformed MIME message:
        // ("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 2154 70 NIL NIL NIL)
        if ($headers->ctype && $headers->ctype != 'text/plain'
            && $structure[0] == 'text' && $structure[1] == 'plain') {
            return false;
        // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL)
        if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain'
            && strtolower($structure[0].'/'.$structure[1]) == 'text/plain') {
            // we can handle single-part messages, by simple fix in structure (#1486898)
            if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
                $structure[0] = $m[1];
                $structure[1] = $m[2];
            }
            else
                return false;
        }
        $struct = &$this->_structure_part($structure);
@@ -1709,7 +1746,7 @@
     *
     * @access private
     */
    function &_structure_part($part, $count=0, $parent='', $mime_headers=null, $raw_headers=null)
    function &_structure_part($part, $count=0, $parent='', $mime_headers=null)
    {
        $struct = new rcube_message_part;
        $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
@@ -1717,6 +1754,18 @@
        // multipart
        if (is_array($part[0])) {
            $struct->ctype_primary = 'multipart';
        /* RFC3501: BODYSTRUCTURE fields of multipart part
            part1 array
            part2 array
            part3 array
            ....
            1. subtype
            2. parameters (optional)
            3. description (optional)
            4. language (optional)
            5. location (optional)
        */
            // find first non-array entry
            for ($i=1; $i<count($part); $i++) {
@@ -1729,19 +1778,18 @@
            $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
            // build parts list for headers pre-fetching
            for ($i=0, $count=0; $i<count($part); $i++) {
                if (is_array($part[$i]) && count($part[$i]) > 3) {
                    // fetch message headers if message/rfc822
                    // or named part (could contain Content-Location header)
                    if (!is_array($part[$i][0])) {
                        $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
                        if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') {
                            $raw_part_headers[] = $tmp_part_id;
                            $mime_part_headers[] = $tmp_part_id;
                        }
                        else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) {
                            $mime_part_headers[] = $tmp_part_id;
                        }
            for ($i=0; $i<count($part); $i++) {
                if (!is_array($part[$i]))
                    break;
                // fetch message headers if message/rfc822
                // or named part (could contain Content-Location header)
                if (!is_array($part[$i][0])) {
                    $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
                    if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') {
                        $mime_part_headers[] = $tmp_part_id;
                    }
                    else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) {
                        $mime_part_headers[] = $tmp_part_id;
                    }
                }
            }
@@ -1753,22 +1801,39 @@
                $mime_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox,
                    $this->_msg_id, $mime_part_headers);
            }
            // we'll need a real content-type of message/rfc822 part
            if ($raw_part_headers) {
                $raw_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox,
                    $this->_msg_id, $raw_part_headers, false);
            }
            $struct->parts = array();
            for ($i=0, $count=0; $i<count($part); $i++) {
                if (is_array($part[$i]) && count($part[$i]) > 3) {
                    $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
                    $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id,
                    $mime_part_headers[$tmp_part_id], $raw_part_headers[$tmp_part_id]);
                }
                if (!is_array($part[$i]))
                    break;
                $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
                $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id,
                    $mime_part_headers[$tmp_part_id]);
            }
            return $struct;
        }
        /* RFC3501: BODYSTRUCTURE fields of non-multipart part
            0. type
            1. subtype
            2. parameters
            3. id
            4. description
            5. encoding
            6. size
          -- text
            7. lines
          -- message/rfc822
            7. envelope structure
            8. body structure
            9. lines
          --
            x. md5 (optional)
            x. disposition (optional)
            x. language (optional)
            x. location (optional)
        */
        // regular part
        $struct->ctype_primary = strtolower($part[0]);
@@ -1796,9 +1861,11 @@
            $struct->size = intval($part[6]);
        // read part disposition
        $di = count($part) - 2;
        if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) ||
            (is_array($part[--$di]) && count($part[$di]) == 2)) {
        $di = 8;
        if ($struct->ctype_primary == 'text') $di += 1;
        else if ($struct->mimetype == 'message/rfc822') $di += 3;
        if (is_array($part[$di]) && count($part[$di]) == 2) {
            $struct->disposition = strtolower($part[$di][0]);
            if (is_array($part[$di][1]))
@@ -1806,12 +1873,14 @@
                    $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
        }
        // get child parts
        // get message/rfc822's child-parts
        if (is_array($part[8]) && $di != 8) {
            $struct->parts = array();
            for ($i=0, $count=0; $i<count($part[8]); $i++)
                if (is_array($part[8][$i]) && count($part[8][$i]) > 5)
                    $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
            for ($i=0, $count=0; $i<count($part[8]); $i++) {
                if (!is_array($part[8][$i]))
                    break;
                $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
            }
        }
        // get part ID
@@ -1831,24 +1900,24 @@
            }
            $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers;
            // get real headers for message of type 'message/rfc822'
            // get real content-type of message/rfc822
            if ($struct->mimetype == 'message/rfc822') {
                if (empty($raw_headers)) {
                    $raw_headers = $this->conn->fetchMIMEHeaders(
                        $this->mailbox, $this->_msg_id, (array)$struct->mime_id, false);
                }
                $struct->real_headers = $this->_parse_headers($raw_headers);
                // get real content-type of message/rfc822
                if (preg_match('/^([a-z0-9_\/-]+)/i', $struct->real_headers['content-type'], $matches)) {
                    $struct->real_mimetype = strtolower($matches[1]);
                // single-part
                if (!is_array($part[8][0]))
                    $struct->real_mimetype = strtolower($part[8][0] . '/' . $part[8][1]);
                // multi-part
                else {
                    for ($n=0; $n<count($part[8]); $n++)
                        if (!is_array($part[8][$n]))
                            break;
                    $struct->real_mimetype = 'multipart/' . strtolower($part[8][$n]);
                }
            }
        }
        if ($struct->ctype_primary=='message') {
            if (is_array($part[8]) && $di != 8 && empty($struct->parts))
                $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
            if ($struct->ctype_primary == 'message' && empty($struct->parts)) {
                if (is_array($part[8]) && $di != 8)
                    $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
            }
        }
        // normalize filename property
@@ -2034,7 +2103,7 @@
            return true;
        // convert charset (if text or message part)
        if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') {
        if ($body && ($o_part->ctype_primary == 'text' || $o_part->ctype_primary == 'message')) {
            // assume default if no charset specified
            if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii')
                $o_part->charset = $this->default_charset;
@@ -2512,7 +2581,7 @@
     * --------------------------------*/
    /**
     * Public method for mailbox listing.
     * Public method for listing subscribed folders
     *
     * Converts mailbox name with root dir first
     *
@@ -2552,16 +2621,16 @@
     */
    private function _list_mailboxes($root='', $filter='*')
    {
        $a_defaults = $a_out = array();
        // get cached folder list
        $a_mboxes = $this->get_cache('mailboxes');
        if (is_array($a_mboxes))
            return $a_mboxes;
        $a_defaults = $a_out = array();
        // Give plugins a chance to provide a list of mailboxes
        $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes',
            array('root'=>$root,'filter'=>$filter));
        $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list',
            array('root' => $root, 'filter' => $filter, 'mode' => 'LSUB'));
        if (isset($data['folders'])) {
            $a_folders = $data['folders'];
@@ -2585,17 +2654,26 @@
     * Get a list of all folders available on the IMAP server
     *
     * @param string IMAP root dir
     * @param string Optional filter for mailbox listing
     * @return array Indexed array with folder names
     */
    function list_unsubscribed($root='')
    function list_unsubscribed($root='', $filter='*')
    {
        static $a_folders;
        // Give plugins a chance to provide a list of mailboxes
        $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list',
            array('root' => $root, 'filter' => $filter, 'mode' => 'LIST'));
        if (is_array($a_folders))
            return $a_folders;
        if (isset($data['folders'])) {
            $a_mboxes = $data['folders'];
        }
        else {
            // retrieve list of folders from IMAP server
            $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), $filter);
        }
        // retrieve list of folders from IMAP server
        $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), '*');
        $a_folders = array();
        if (!is_array($a_mboxes))
            $a_mboxes = array();
        // modify names with root dir
        foreach ($a_mboxes as $idx => $mbox_name) {
@@ -3370,43 +3448,6 @@
        }
        return $out;
    }
    /**
     * Decode a Microsoft Outlook TNEF part (winmail.dat)
     *
     * @param object rcube_message_part Message part to decode
     * @param string UID of the message
     * @return array List of rcube_message_parts extracted from windmail.dat
     */
    function tnef_decode(&$part, $uid)
    {
        if (!isset($part->body))
            $part->body = $this->get_message_part($uid, $part->mime_id, $part);
        require_once('lib/tnef_decoder.inc');
        $pid = 0;
        $tnef_parts = array();
        $tnef_arr = tnef_decode($part->body);
        foreach ($tnef_arr as $winatt) {
            $tpart = new rcube_message_part;
            $tpart->filename = $winatt["name"];
            $tpart->encoding = 'stream';
            $tpart->ctype_primary = $winatt["type0"];
            $tpart->ctype_secondary = $winatt["type1"];
            $tpart->mimetype = strtolower($winatt["type0"] . "/" . $winatt["type1"]);
            $tpart->mime_id = "winmail." . $part->mime_id . ".$pid";
            $tpart->size = $winatt["size"];
            $tpart->body = $winatt['stream'];
            $tnef_parts[] = $tpart;
            $pid++;
        }
        return $tnef_parts;
    }