source: subversion/branches/devel-vnext/program/include/rcube_imap.inc @ 645

Last change on this file since 645 was 645, checked in by till, 6 years ago

+ macbay_filter

  • added helper class
  • restructured code
  • less ajax, more standard PHP-form-handling

+ changed bugs.inc

  • created a class rc_bugs
  • fixed all references to raise_error

# fixed possible XSS (plugin inclusion) index.php

  • Property svn:executable set to *
File size: 77.4 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/rcube_imap.inc                                        |
6 |                                                                       |
7 | This file is part of the RoundCube Webmail client                     |
8 | Copyright (C) 2005-2006, RoundCube Dev. - Switzerland                 |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   IMAP wrapper that implements the Iloha IMAP Library (IIL)           |
13 |   See http://ilohamail.org/ for details                               |
14 |                                                                       |
15 +-----------------------------------------------------------------------+
16 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17 +-----------------------------------------------------------------------+
18
19 $Id: rcube_imap.inc 561 2007-05-17 15:18:12Z thomasb $
20
21*/
22
23
24/**
25 * Obtain classes from the Iloha IMAP library
26 */
27require_once 'lib/imap.inc';
28require_once 'lib/mime.inc';
29
30
31/**
32 * Interface class for accessing an IMAP server
33 *
34 * This is a wrapper that implements the Iloha IMAP Library (IIL)
35 *
36 * @package    RoundCube Webmail
37 * @author     Thomas Bruederli <roundcube@gmail.com>
38 * @version    1.36
39 * @link       http://ilohamail.org
40 */
41class rcube_imap
42{
43    var $db;
44    var $conn;
45    var $root_ns = '';
46    var $root_dir = '';
47    var $mailbox = 'INBOX';
48    var $list_page = 1;
49    var $page_size = 10;
50    var $sort_field = 'date';
51    var $sort_order = 'DESC';
52    var $delimiter = NULL;
53    var $caching_enabled = FALSE;
54    var $default_folders = array('INBOX');
55    var $default_folders_lc = array('inbox');
56    var $cache = array();
57    var $cache_keys = array();
58    var $cache_changes = array();
59    var $uid_id_map = array();
60    var $msg_headers = array();
61    var $capabilities = array();
62    var $skip_deleted = FALSE;
63    var $search_set = NULL;
64    var $search_subject = '';
65    var $search_string = '';
66    var $search_charset = '';
67    var $debug_level = 1;
68
69
70    /**
71     * Object constructor
72     *
73     * @acccess public
74     * @param   object  Database connection
75     */
76    function __construct($db_conn)
77    {
78        $this->db = $db_conn;
79    }
80
81
82    /**
83     * PHP 4 object constructor
84     *
85     * @access public
86     * @see    rcube_imap::__construct
87     */
88    function rcube_imap($db_conn)
89    {
90        $this->__construct($db_conn);
91    }
92
93
94    /**
95     * Connect to an IMAP server
96     *
97     * @param  string   Host to connect
98     * @param  string   Username for IMAP account
99     * @param  string   Password for IMAP account
100     * @param  number   Port to connect to
101     * @param  boolean  Use SSL connection
102     * @return boolean  TRUE on success, FALSE on failure
103     * @access public
104     */
105    function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
106    {
107        global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
108
109        // check for Open-SSL support in PHP build
110        if ($use_ssl && in_array('openssl', get_loaded_extensions())) {
111            $ICL_SSL = TRUE;
112        }
113        elseif ($use_ssl) {
114            rc_bugs::raise_error(
115                array(
116                    'code' => 403,
117                    'type' => 'imap',
118                    'file' => __FILE__,
119                    'message' => 'Open SSL not available;'
120                ),
121                TRUE,
122                FALSE
123            );
124            $port = 143;
125        }
126
127        $ICL_PORT = $port;
128        $IMAP_USE_INTERNAL_DATE = false;
129
130        $this->conn = iil_Connect($host, $user, $pass, array('imap' => 'check'));
131        $this->host = $host;
132        $this->user = $user;
133        $this->pass = $pass;
134        $this->port = $port;
135        $this->ssl = $use_ssl;
136
137        // print trace mesages
138        if ($this->conn && ($this->debug_level & 8)) {
139            console($this->conn->message);
140        }
141        // write error log
142        else if (!$this->conn && $GLOBALS['iil_error']) {
143            rc_bugs::raise_error(array('code' => 403,
144                       'type' => 'imap',
145                       'message' => $GLOBALS['iil_error']), TRUE, FALSE);
146        }
147
148        // get server properties
149        if ($this->conn === FALSE) {
150            return FALSE;
151        }
152        $this->_parse_capability($this->conn->capability);
153
154        if (empty($this->conn->delimiter) === FALSE) {
155            $this->delimiter = $this->conn->delimiter;
156        }
157        if (empty($this->conn->rootdir) === FALSE) {
158            $this->set_rootdir($this->conn->rootdir);
159            $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
160        }
161        return TRUE;
162    }
163
164
165    /**
166     * Close IMAP connection
167     * Usually done on script shutdown
168     *
169     * @access public
170     */
171    function close()
172    {
173        if ($this->conn) {
174            iil_Close($this->conn);
175        }
176    }
177
178
179    /**
180     * Close IMAP connection and re-connect
181     * This is used to avoid some strange socket errors when talking to Courier IMAP
182     *
183     * @access public
184     */
185    function reconnect()
186    {
187        $this->close();
188        $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
189    }
190
191
192    /**
193     * Set a root folder for the IMAP connection.
194     *
195     * Only folders within this root folder will be displayed
196     * and all folder paths will be translated using this folder name
197     *
198     * @param  string   Root folder
199     * @access public
200     * @return void
201     */
202    function set_rootdir($root)
203    {
204        if (ereg('[\.\/]$', $root)) {
205            // (substr($root, -1, 1)==='/')
206            $root = substr($root, 0, -1);
207        }
208        $this->root_dir = $root;
209
210        if (empty($this->delimiter)) {
211            $this->get_hierarchy_delimiter();
212        }
213    }
214
215
216    /**
217     * This list of folders will be listed above all other folders
218     *
219     * @param  array  Indexed list of folder names
220     * @access public
221     */
222    function set_default_mailboxes($arr)
223    {
224        if (is_array($arr)) {
225            $this->default_folders = $arr;
226            $this->default_folders_lc = array();
227
228            // add inbox if not included
229            if (!in_array_nocase('INBOX', $this->default_folders)) {
230                array_unshift($this->default_folders, 'INBOX');
231            }
232            // create a second list with lower cased names
233            foreach ($this->default_folders as $mbox) {
234                $this->default_folders_lc[] = strtolower($mbox);
235            }
236        }
237    }
238
239
240    /**
241     * Set internal mailbox reference.
242     *
243     * All operations will be perfomed on this mailbox/folder
244     *
245     * @param  string  Mailbox/Folder name
246     * @access public
247     */
248    function set_mailbox($new_mbox)
249    {
250        $mailbox = $this->_mod_mailbox($new_mbox);
251
252        if ($this->mailbox == $mailbox) {
253            return;
254        }
255        $this->mailbox = $mailbox;
256
257        // clear messagecount cache for this mailbox
258        $this->_clear_messagecount($mailbox);
259    }
260
261
262    /**
263     * Set internal list page
264     *
265     * @param  number  Page number to list
266     * @access public
267     */
268    function set_page($page)
269    {
270        $this->list_page = (int)$page;
271    }
272
273
274    /**
275     * Set internal page size
276     *
277     * @param  number  Number of messages to display on one page
278     * @access public
279     */
280    function set_pagesize($size)
281    {
282        $this->page_size = (int)$size;
283    }
284
285
286    /**
287     * Save a set of message ids for future message listing methods
288     *
289     * @param  array  List of IMAP fields to search in
290     * @param  string Search string
291     * @param  array  List of message ids or NULL if empty
292     */
293    function set_search_set($subject, $str=null, $msgs=null, $charset=null)
294    {
295        if (is_array($subject) && $str == null && $msgs == null) {
296            list($subject, $str, $msgs, $charset) = $subject;
297        }
298        if ($msgs != null && !is_array($msgs)) {
299            $msgs = split(',', $msgs);
300        }
301        $this->search_subject = $subject;
302        $this->search_string = $str;
303        $this->search_set = is_array($msgs) ? $msgs : NULL;
304        $this->search_charset = $charset;
305    }
306
307
308    /**
309     * Return the saved search set as hash array
310     */
311    function get_search_set()
312    {
313        return array(
314                $this->search_subject,
315                $this->search_string,
316                $this->search_set,
317                $this->search_charset
318        );
319    }
320
321
322    /**
323     * Returns the currently used mailbox name
324     *
325     * @return  string Name of the mailbox/folder
326     * @access  public
327     */
328    function get_mailbox_name()
329    {
330        return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
331    }
332
333
334    /**
335     * Returns the IMAP server's capability
336     *
337     * @param   string  Capability name
338     * @return  mixed   Capability value or TRUE if supported, FALSE if not
339     * @access  public
340     */
341    function get_capability($cap)
342    {
343        $cap = strtoupper($cap);
344        return $this->capabilities[$cap];
345    }
346
347
348    /**
349     * Returns the delimiter that is used by the IMAP server for folder separation
350     *
351     * @return  string  Delimiter string
352     * @access  public
353     */
354    function get_hierarchy_delimiter()
355    {
356        if ($this->conn && empty($this->delimiter)) {
357            $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
358        }
359        if (empty($this->delimiter)) {
360            $this->delimiter = '/';
361        }
362        return $this->delimiter;
363    }
364
365
366    /**
367     * Public method for mailbox listing.
368     *
369     * Converts mailbox name with root dir first
370     *
371     * @param   string  Optional root folder
372     * @param   string  Optional filter for mailbox listing
373     * @return  array   List of mailboxes/folders
374     * @access  public
375     */
376    function list_mailboxes($root='', $filter='*')
377    {
378        $a_out = array();
379        $a_mboxes = $this->_list_mailboxes($root, $filter);
380
381        foreach ($a_mboxes as $mbox_row) {
382            $name = $this->_mod_mailbox($mbox_row, 'out');
383            if (empty($name) === false) {
384                $a_out[] = $name;
385            }
386            else {
387                tfk_debug('Filtered out: ' . $mbox_row);
388            }
389        }
390
391        // INBOX should always be available
392        if (!in_array_nocase('INBOX', $a_out)) {
393            array_unshift($a_out, 'INBOX');
394        }
395
396        // sort mailboxes
397        $a_out = $this->_sort_mailbox_list($a_out);
398
399        return $a_out;
400    }
401
402
403    /**
404     * Private method for mailbox listing
405     *
406     * @return  array   List of mailboxes/folders
407     * @access  private
408     * @see     rcube_imap::list_mailboxes
409     */
410    function _list_mailboxes($root='', $filter='*')
411    {
412        $registry = rc_registry::getInstance();
413        $CONFIG   = $registry->get('CONFIG', 'core');
414
415        $a_defaults = $a_out = array();
416
417        // get cached folder list
418        $a_mboxes = $this->get_cache('mailboxes');
419        if (is_array($a_mboxes) === true) {
420            return $a_mboxes;
421        }
422        if (isset($CONFIG['display_all_mailboxes']) === false || $CONFIG['display_all_mailboxes'] == FALSE) {
423            // retrieve list of folders from IMAP server
424            $a_folders = iil_C_ListSubscribed(
425                            $this->conn,
426                            $this->_mod_mailbox($root),
427                            $filter
428            );
429            if (is_array($a_folders) === false || !sizeof($a_folders)) {
430                $a_folders = array();
431            }
432        }
433        else {
434            $a_folders = $this->list_unsubscribed($root);
435        }
436        // write mailboxlist to cache
437        $this->update_cache('mailboxes', $a_folders);
438
439        return $a_folders;
440    }
441
442
443    /**
444     * Get message count for a specific mailbox
445     *
446     * @param   string   Mailbox/folder name
447     * @param   string   Mode for count [ALL|UNSEEN|RECENT]
448     * @param   boolean  Force reading from server and update cache
449     * @return  number   Number of messages
450     * @access  public
451     */
452    function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
453    {
454        $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
455        return $this->_messagecount($mailbox, $mode, $force);
456    }
457
458
459    /**
460     * Private method for getting nr of messages
461     *
462     * @access  private
463     * @see     rcube_imap::messagecount
464     */
465    function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
466    {
467        $a_mailbox_cache = FALSE;
468        $mode = strtoupper($mode);
469
470        if (empty($mailbox)) {
471            $mailbox = $this->mailbox;
472        }
473        // count search set
474        if ($this->search_set && $mailbox == $this->mailbox && $mode == 'ALL') {
475            return count($this->search_set);
476        }
477        $a_mailbox_cache = $this->get_cache('messagecount');
478
479        // return cached value
480        if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode])) {
481            return $a_mailbox_cache[$mailbox][$mode];
482        }
483        // RECENT count is fetched abit different
484        if ($mode == 'RECENT') {
485            $count = iil_C_CheckForRecent($this->conn, $mailbox);
486        }
487        // use SEARCH for message counting
488        else if ($this->skip_deleted)
489        {
490            $search_str = "ALL UNDELETED";
491
492            // get message count and store in cache
493            if ($mode == 'UNSEEN') {
494                $search_str .= " UNSEEN";
495            }
496            // get message count using SEARCH
497            // not very performant but more precise (using UNDELETED)
498            $count = 0;
499            $index = $this->_search_index($mailbox, $search_str);
500            if (is_array($index) === TRUE) {
501                $str = implode(",", $index);
502                if (!empty($str)) {
503                    $count = count($index);
504                }
505            }
506        }
507        else {
508            if ($mode == 'UNSEEN') {
509                $count = iil_C_CountUnseen($this->conn, $mailbox);
510            }
511            else {
512                $count = iil_C_CountMessages($this->conn, $mailbox);
513            }
514        }
515
516        if (!is_array($a_mailbox_cache[$mailbox])) {
517            $a_mailbox_cache[$mailbox] = array();
518        }
519        $a_mailbox_cache[$mailbox][$mode] = (int)$count;
520
521        // write back to cache
522        $this->update_cache('messagecount', $a_mailbox_cache);
523
524        return (int)$count;
525    }
526
527
528    /**
529     * Public method for listing headers
530     * convert mailbox name with root dir first
531     *
532     * @param   string   Mailbox/folder name
533     * @param   number   Current page to list
534     * @param   string   Header field to sort by
535     * @param   string   Sort order [ASC|DESC]
536     * @return  array    Indexed array with message header objects
537     * @access  public
538     */
539    function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
540    {
541        $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
542        return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
543    }
544
545
546    /**
547     * Private method for listing message headers
548     *
549     * @access  private
550     * @see     rcube_imap::list_headers
551     */
552    function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
553    {
554        if (!strlen($mailbox)) {
555            return array();
556        }
557        // use saved message set
558        if ($this->search_set && $mailbox == $this->mailbox) {
559            return $this->_list_header_set($mailbox, $this->search_set, $page, $sort_field, $sort_order);
560        }
561        if (is_null($sort_field) === FALSE) {
562            $this->sort_field = $sort_field;
563        }
564        if (is_null($sort_order) === FALSE) {
565            $this->sort_order = strtoupper($sort_order);
566        }
567
568        $max = $this->_messagecount($mailbox);
569        $start_msg = ($this->list_page-1) * $this->page_size;
570
571        list($begin, $end) = $this->_get_message_range($max, $page);
572
573        // mailbox is empty
574        if ($begin >= $end) {
575            return array();
576        }
577
578        $headers_sorted = FALSE;
579        $cache_key = $mailbox.'.msg';
580        $cache_status = $this->check_cache_status($mailbox, $cache_key);
581
582        // cache is OK, we can get all messages from local cache
583        if ($cache_status>0) {
584            $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
585            $headers_sorted = TRUE;
586        }
587        // cache is dirty, sync it
588        elseif ($this->caching_enabled && $cache_status==-1 && !$recursive) {
589            $this->sync_header_index($mailbox);
590            return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
591        }
592        else {
593            // retrieve headers from IMAP
594            if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : ''))) {
595                $msgs = $msg_index[$begin];
596                for ($i=$begin+1; $i < $end; $i++) {
597                    $msgs = $msgs.','.$msg_index[$i];
598                }
599            }
600            else {
601                $msgs = sprintf("%d:%d", $begin+1, $end);
602
603                $i = 0;
604                for ($msg_seqnum = $begin; $msg_seqnum <= $end; $msg_seqnum++) {
605                    $msg_index[$i++] = $msg_seqnum;
606                }
607            }
608
609            // use this class for message sorting
610            $sorter = new rcube_header_sorter();
611            $sorter->set_sequence_numbers($msg_index);
612
613            // fetch reuested headers from server
614            $a_msg_headers = array();
615            $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
616
617            // delete cached messages with a higher index than $max+1
618            // Changed $max to $max+1 to fix this bug : #1484295
619            $this->clear_message_cache($cache_key, $max + 1);
620
621
622            // kick child process to sync cache
623            // ...
624
625        }
626
627
628        // return empty array if no messages found
629            if (!is_array($a_msg_headers) || empty($a_msg_headers)) {
630                    return array();
631        }
632
633        // if not already sorted
634        if (!$headers_sorted) {
635            $sorter->sort_headers($a_msg_headers);
636
637            if ($this->sort_order == 'DESC') {
638                $a_msg_headers = array_reverse($a_msg_headers);
639            }
640        }
641        return array_values($a_msg_headers);
642    }
643
644
645
646    /**
647     * Public method for listing a specific set of headers
648     * convert mailbox name with root dir first
649     *
650     * @param   string   Mailbox/folder name
651     * @param   array    List of message ids to list
652     * @param   number   Current page to list
653     * @param   string   Header field to sort by
654     * @param   string   Sort order [ASC|DESC]
655     * @return  array    Indexed array with message header objects
656     * @access  public
657     */
658    function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
659    {
660        $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
661        return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);
662    }
663
664
665    /**
666     * Private method for listing a set of message headers
667     *
668     * @access  private
669     * @see     rcube_imap::list_header_set
670     */
671    function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
672    {
673        // also accept a comma-separated list of message ids
674        if (is_string($msgs)) {
675            $msgs = split(',', $msgs);
676        }
677        if (!strlen($mailbox) || empty($msgs)) {
678            return array();
679        }
680        if ($sort_field!=NULL) {
681            $this->sort_field = $sort_field;
682        }
683        if ($sort_order!=NULL) {
684            $this->sort_order = strtoupper($sort_order);
685        }
686        $max = count($msgs);
687        $start_msg = ($this->list_page-1) * $this->page_size;
688
689        // fetch reuested headers from server
690        $a_msg_headers = array();
691        $this->_fetch_headers(
692                    $mailbox,
693                    join(',', $msgs),
694                    $a_msg_headers,
695                    NULL
696        );
697
698        // return empty array if no messages found
699        if (!is_array($a_msg_headers) || empty($a_msg_headers))
700            return array();
701
702        // if not already sorted
703        $a_msg_headers = iil_SortHeaders(
704                            $a_msg_headers,
705                            $this->sort_field,
706                            $this->sort_order
707        );
708
709        // only return the requested part of the set
710        return array_slice(
711                    array_values($a_msg_headers),
712                    $start_msg,
713                    min($max-$start_msg, $this->page_size)
714        );
715    }
716
717
718    /**
719     * Helper function to get first and last index of the requested set
720     *
721     * @param  number  message count
722     * @param  mixed   page number to show, or string 'all'
723     * @return array   array with two values: first index, last index
724     * @access private
725     */
726    function _get_message_range($max, $page)
727    {
728        $start_msg = ($this->list_page-1) * $this->page_size;
729
730        if ($page=='all') {
731            $begin = 0;
732            $end   = $max;
733        }
734        elseif ($this->sort_order=='DESC') {
735            $begin = $max - $this->page_size - $start_msg;
736            $end   = $max - $start_msg;
737        }
738        else {
739            $begin = $start_msg;
740            $end   = $start_msg + $this->page_size;
741        }
742
743        if ($begin < 0) {
744            $begin = 0;
745        }
746        if ($end < 0) {
747            $end = $max;
748        }
749        if ($end > $max) {
750            $end = $max;
751        }
752        return array($begin, $end);
753    }
754
755
756
757  /**
758   * Fetches message headers
759   * Used for loop
760   *
761   * @param  string  Mailbox name
762   * @param  string  Message index to fetch
763   * @param  array   Reference to message headers array
764   * @param  array   Array with cache index
765   * @return number  Number of deleted messages
766   * @access private
767   */
768  function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
769    {
770    // cache is incomplete
771    $cache_index = $this->get_message_cache_index($cache_key);
772
773    // fetch reuested headers from server
774    $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
775    $deleted_count = 0;
776
777    if (!empty($a_header_index))
778      {
779      foreach ($a_header_index as $i => $headers)
780        {
781        if ($headers->deleted && $this->skip_deleted)
782          {
783          // delete from cache
784          if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
785            $this->remove_message_cache($cache_key, $headers->id);
786
787          $deleted_count++;
788          continue;
789          }
790
791        // add message to cache
792        if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
793          $this->add_message_cache($cache_key, $headers->id, $headers);
794
795        $a_msg_headers[$headers->uid] = $headers;
796        }
797      }
798
799    return $deleted_count;
800    }
801
802
803  /**
804   * Return sorted array of message UIDs
805   *
806   * @param string Mailbox to get index from
807   * @param string Sort column
808   * @param string Sort order [ASC, DESC]
809   * @return array Indexed array with message ids
810   */
811  function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
812    {
813    if ($sort_field!=NULL)
814      $this->sort_field = $sort_field;
815    if ($sort_order!=NULL)
816      $this->sort_order = strtoupper($sort_order);
817
818    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
819    $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
820
821    // have stored it in RAM
822    if (isset($this->cache[$key]))
823      return $this->cache[$key];
824
825    // check local cache
826    $cache_key = $mailbox.'.msg';
827    $cache_status = $this->check_cache_status($mailbox, $cache_key);
828
829    // cache is OK
830    if ($cache_status>0)
831      {
832      $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
833      return array_values($a_index);
834      }
835
836
837    // fetch complete message index
838    $msg_count = $this->_messagecount($mailbox);
839    if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
840      {
841      if ($this->sort_order == 'DESC')
842        $a_index = array_reverse($a_index);
843
844      $this->cache[$key] = $a_index;
845
846      }
847    else
848      {
849      $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
850      $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
851
852      if ($this->sort_order=="ASC")
853        asort($a_index);
854      else if ($this->sort_order=="DESC")
855        arsort($a_index);
856
857      $i = 0;
858      $this->cache[$key] = array();
859      foreach ($a_index as $index => $value)
860        $this->cache[$key][$i++] = $a_uids[$index];
861      }
862
863    return $this->cache[$key];
864    }
865
866
867    function sync_header_index($mailbox)
868    {
869        $cache_key = $mailbox.'.msg';
870        $cache_index = $this->get_message_cache_index($cache_key);
871        $msg_count = $this->_messagecount($mailbox);
872
873        // fetch complete message index
874        $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
875
876        foreach ($a_message_index as $id => $uid) {
877            // message in cache at correct position
878            if ($cache_index[$id] == $uid) {
879                unset($cache_index[$id]);
880                continue;
881            }
882
883            // message in cache but in wrong position
884            if (in_array((string)$uid, $cache_index, TRUE)) {
885                unset($cache_index[$id]);
886            }
887
888            // other message at this position
889            if (isset($cache_index[$id])) {
890                $this->remove_message_cache($cache_key, $id);
891                unset($cache_index[$id]);
892            }
893
894
895            // fetch complete headers and add to cache
896            $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
897            $this->add_message_cache($cache_key, $headers->id, $headers);
898        }
899
900        // those ids that are still in cache_index have been deleted
901        if (empty($cache_index) === false) {
902            foreach ($cache_index as $id => $uid) {
903                $this->remove_message_cache($cache_key, $id);
904            }
905        }
906    }
907
908
909    /**
910     * Invoke search request to IMAP server
911     *
912     * @param  string  mailbox name to search in
913     * @param  string  search criteria (ALL, TO, FROM, SUBJECT, etc)
914     * @param  string  search string
915     * @return array   search results as list of message ids
916     * @access public
917     */
918    function search($mbox_name='', $criteria='ALL', $str=NULL, $charset=NULL)
919    {
920        $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
921
922        // have an array of criterias => execute multiple searches
923        if (is_array($criteria) && $str) {
924            $results = array();
925            foreach ($criteria as $crit) {
926                if ($search_result = $this->search($mbox_name, $crit, $str, $charset)) {
927                    $results = array_merge($results, $search_result);
928                }
929            }
930            $results = array_unique($results);
931            $this->set_search_set($criteria, $str, $results, $charset);
932            return $results;
933        }
934        if ($str && $criteria) {
935            $search = (!empty($charset) ? "CHARSET $charset " : '');
936            $search.= sprintf("%s {%d}\r\n%s", $criteria, strlen($str), $str);
937            $results = $this->_search_index($mailbox, $search);
938
939            // try search with ISO charset (should be supported by server)
940            if (empty($results) && !empty($charset) && $charset!='ISO-8859-1') {
941                $results = $this->search(
942                                $mbox_name,
943                                $criteria,
944                                rc_main::rcube_charset_convert($str, $charset, 'ISO-8859-1'),
945                                'ISO-8859-1'
946                );
947            }
948            $this->set_search_set($criteria, $str, $results, $charset);
949            return $results;
950        }
951        return $this->_search_index($mailbox, $criteria);
952    }
953
954
955    /**
956     * Private search method
957     *
958     * @return array   search results as list of message ids
959     * @access private
960     * @see rcube_imap::search()
961     */
962    function _search_index($mailbox, $criteria='ALL')
963    {
964        $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
965        // clean message list (there might be some empty entries)
966        if (is_array($a_messages) === false) {
967            return $a_messages;
968        }
969        foreach ($a_messages as $i => $val) {
970            if (empty($val)) {
971                unset($a_messages[$i]);
972            }
973        }
974        return $a_messages;
975    }
976
977
978    /**
979     * Refresh saved search set
980     */
981    function refresh_search()
982    {
983        if (!empty($this->search_subject) && !empty($this->search_string)) {
984            $this->search_set = $this->search(
985                                    '',
986                                    $this->search_subject,
987                                    $this->search_string,
988                                    $this->search_charset
989            );
990        }
991        return $this->get_search_set();
992    }
993
994
995    /**
996     * Return message headers object of a specific message
997     *
998     * @param int     Message ID
999     * @param string  Mailbox to read from
1000     * @param boolean True if $id is the message UID
1001     * @return object Message headers representation
1002     * @deprecated
1003     */
1004    function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
1005    {
1006        $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1007        $uid = $is_uid ? $id : $this->_id2uid($id);
1008
1009        // get cached headers
1010        if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) {
1011            return $headers;
1012        }
1013        $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid);
1014        //tfk_debug(var_export($headers, true));
1015        // write headers cache
1016        if ($headers) {
1017            if ($is_uid) {
1018                $this->uid_id_map[$mbox_name][$uid] = $headers->id;
1019            }
1020            $this->add_message_cache($mailbox.'.msg', $headers->id, $headers);
1021        }
1022        //tfk_debug(var_export($headers));
1023        return $headers;
1024    }
1025
1026
1027    /**
1028     * Fetch body structure from the IMAP server and build
1029     * an object structure similar to the one generated by PEAR::Mail_mimeDecode
1030     *
1031     * @param Int Message UID to fetch
1032     * @return object Standard object tree or False on failure
1033     */
1034    function &get_structure($uid)
1035    {
1036        $cache_key = $this->mailbox.'.msg';
1037        $headers = &$this->get_cached_message($cache_key, $uid, true);
1038
1039        // return cached message structure
1040        if (is_object($headers) && is_object($headers->structure)) {
1041            return $headers->structure;
1042        }
1043        // resolve message sequence number
1044        if (!($msg_id = $this->_uid2id($uid))) {
1045            return FALSE;
1046        }
1047        $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
1048        $structure     = iml_GetRawStructureArray($structure_str);
1049        $struct        = false;
1050
1051        // parse structure and add headers
1052        if (empty($structure) === true) {
1053            return $struct;
1054        }
1055        $this->_msg_id = $msg_id;
1056        $headers = $this->get_headers($msg_id, NULL, FALSE);
1057
1058        $struct = &$this->_structure_part($structure);
1059        //$struct->headers = get_object_vars($headers);
1060        $struct->headers = $headers;
1061        // don't trust given content-type
1062        if (empty($struct->parts) && !empty($struct->headers->ctype)) {
1063            $struct->mime_id = '1';
1064            $struct->mimetype = strtolower($struct->headers->ctype);
1065            list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
1066        }
1067
1068        // write structure to cache
1069        if ($this->caching_enabled) {
1070            $this->add_message_cache($cache_key, $msg_id, $headers, $struct);
1071        }
1072        return $struct;
1073    }
1074
1075
1076    /**
1077     * Build message part object
1078     *
1079     * @access private
1080     */
1081    function &_structure_part($part, $count=0, $parent='')
1082    {
1083        $struct = new rcube_message_part;
1084        $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
1085
1086        // multipart
1087        if (is_array($part[0])) {
1088            $struct->ctype_primary = 'multipart';
1089
1090            // find first non-array entry
1091            for ($i=1; count($part); $i++) {
1092                if (!is_array($part[$i])) {
1093                    $struct->ctype_secondary = strtolower($part[$i]);
1094                    break;
1095                }
1096            }
1097            $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
1098
1099            $struct->parts = array();
1100            for ($i=0, $count=0; $i<count($part); $i++) {
1101                if (is_array($part[$i]) && count($part[$i]) > 5) {
1102                    $struct->parts[] = $this->_structure_part(
1103                                                    $part[$i],
1104                                                    ++$count,
1105                                                    $struct->mime_id
1106                    );
1107                }
1108            }
1109            return $struct;
1110        }
1111
1112
1113        // regular part
1114        $struct->ctype_primary = strtolower($part[0]);
1115        $struct->ctype_secondary = strtolower($part[1]);
1116        $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary;
1117
1118        // read content type parameters
1119        if (is_array($part[2])) {
1120            $struct->ctype_parameters = array();
1121            for ($i=0; $i<count($part[2]); $i+=2) {
1122                $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
1123            }
1124            if (isset($struct->ctype_parameters['charset'])) {
1125                $struct->charset = $struct->ctype_parameters['charset'];
1126            }
1127        }
1128
1129        // read content encoding
1130        if (!empty($part[5]) && $part[5]!='NIL') {
1131            $struct->encoding = strtolower($part[5]);
1132            $struct->headers['content-transfer-encoding'] = $struct->encoding;
1133        }
1134
1135        // get part size
1136        if (!empty($part[6]) && $part[6]!='NIL') {
1137            $struct->size = intval($part[6]);
1138        }
1139        // read part disposition
1140        $di = count($part) - 2;
1141        if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) ||
1142        (is_array($part[--$di]) && count($part[$di]) == 2)
1143        ) {
1144            $struct->disposition = strtolower($part[$di][0]);
1145
1146            if (is_array($part[$di][1])) {
1147                for ($n=0; $n<count($part[$di][1]); $n+=2) {
1148                    $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
1149                }
1150            }
1151        }
1152
1153        // get child parts
1154        if (is_array($part[8]) && $di != 8) {
1155            $struct->parts = array();
1156            for ($i=0, $count=0; $i<count($part[8]); $i++) {
1157                if (is_array($part[8][$i]) && count($part[8][$i]) > 5) {
1158                    $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
1159                }
1160            }
1161        }
1162
1163        // get part ID
1164        if (!empty($part[3]) && $part[3]!='NIL') {
1165            $struct->content_id = $part[3];
1166            $struct->headers['content-id'] = $part[3];
1167
1168            if (empty($struct->disposition)) {
1169                $struct->disposition = 'inline';
1170            }
1171        }
1172
1173        // fetch message headers if message/rfc822
1174        if ($struct->ctype_primary=='message') {
1175            $headers = iil_C_FetchPartBody(
1176                        $this->conn,
1177                        $this->mailbox,
1178                        $this->_msg_id,
1179                        $struct->mime_id.'.HEADER'
1180            );
1181            $struct->headers = $this->_parse_headers($headers);
1182
1183            if (is_array($part[8]) && empty($struct->parts)) {
1184                $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
1185            }
1186        }
1187
1188        // normalize filename property
1189        if (!empty($struct->d_parameters['filename'])) {
1190            $struct->filename = $this->decode_mime_string($struct->d_parameters['filename']);
1191        }
1192        else if (!empty($struct->ctype_parameters['name'])) {
1193            $struct->filename = $this->decode_mime_string($struct->ctype_parameters['name']);
1194        }
1195        else if (!empty($struct->headers['content-description'])) {
1196            $struct->filename = $this->decode_mime_string($struct->headers['content-description']);
1197        }
1198
1199        return $struct;
1200    }
1201
1202
1203  /**
1204   * Return a flat array with references to all parts, indexed by part numbers
1205   *
1206   * @param object Message body structure
1207   * @return Array with part number -> object pairs
1208   */
1209  function get_mime_numbers(&$structure)
1210    {
1211    $a_parts = array();
1212    $this->_get_part_numbers($structure, $a_parts);
1213    return $a_parts;
1214    }
1215
1216
1217  /**
1218   * Helper method for recursive calls
1219   *
1220   * @access
1221   */
1222  function _get_part_numbers(&$part, &$a_parts)
1223    {
1224    if ($part->mime_id)
1225      $a_parts[$part->mime_id] = &$part;
1226
1227    if (is_array($part->parts))
1228      for ($i=0; $i<count($part->parts); $i++)
1229        $this->_get_part_numbers($part->parts[$i], $a_parts);
1230    }
1231
1232
1233  /**
1234   * Fetch message body of a specific message from the server
1235   *
1236   * @param  int    Message UID
1237   * @param  string Part number
1238   * @param  object Part object created by get_structure()
1239   * @param  mixed  True to print part, ressource to write part contents in
1240   * @return Message/part body if not printed
1241   */
1242  function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL)
1243    {
1244    if (!($msg_id = $this->_uid2id($uid)))
1245      return FALSE;
1246
1247    // get part encoding if not provided
1248    if (!is_object($o_part))
1249      {
1250      $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
1251      $structure = iml_GetRawStructureArray($structure_str);
1252      $part_type = iml_GetPartTypeCode($structure, $part);
1253      $o_part = new rcube_message_part;
1254      $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other');
1255      $o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part));
1256      $o_part->charset = iml_GetPartCharset($structure, $part);
1257      }
1258
1259    // TODO: Add caching for message parts
1260
1261    if ($print)
1262      {
1263      iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, ($o_part->encoding=='base64'?3:2));
1264      $body = TRUE;
1265      }
1266    else
1267      {
1268      $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 1);
1269
1270      // decode part body
1271      if ($o_part->encoding=='base64' || $o_part->encoding=='quoted-printable')
1272        $body = $this->mime_decode($body, $o_part->encoding);
1273
1274      // convert charset (if text or message part)
1275      if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message')
1276        {
1277        // assume ISO-8859-1 if no charset specified
1278        if (empty($o_part->charset))
1279          $o_part->charset = 'ISO-8859-1';
1280
1281        $body = rc_main::rcube_charset_convert($body, $o_part->charset);
1282        }
1283      }
1284
1285    return $body;
1286    }
1287
1288
1289    /**
1290     * Fetch message body of a specific message from the server
1291     *
1292     * @param  int    Message UID
1293     * @return Message/part body
1294     * @see    ::get_message_part()
1295     */
1296    function &get_body($uid, $part=1)
1297    {
1298        return $this->get_message_part($uid, $part);
1299    }
1300
1301
1302    /**
1303     * Returns the whole message source as string
1304     *
1305     * @param int  Message UID
1306     * @return Message source string
1307     */
1308    function &get_raw_body($uid)
1309    {
1310        if (!($msg_id = $this->_uid2id($uid))) {
1311            return FALSE;
1312        }
1313        $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
1314        $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
1315
1316        return $body;
1317    }
1318
1319
1320    /**
1321     * Sends the whole message source to stdout
1322     *
1323     * @param int  Message UID
1324     */
1325    function print_raw_body($uid)
1326    {
1327        if (!($msg_id = $this->_uid2id($uid))) {
1328            return FALSE;
1329        }
1330        print iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
1331            flush();
1332            iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2);
1333    }
1334
1335
1336    /**
1337     * Set message flag to one or several messages
1338     *
1339     * @param mixed  Message UIDs as array or as comma-separated string
1340     * @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT
1341     * @return True on success, False on failure
1342     */
1343    function set_flag($uids, $flag)
1344    {
1345        $flag = strtoupper($flag);
1346        $msg_ids = array();
1347        if (!is_array($uids)) {
1348            $uids = explode(',',$uids);
1349        }
1350        foreach ($uids as $uid) {
1351            $msg_ids[$uid] = $this->_uid2id($uid);
1352        }
1353
1354        if ($flag=='UNDELETED') {
1355            $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
1356        }
1357        elseif ($flag=='UNSEEN') {
1358            $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
1359        }
1360        else {
1361            $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
1362        }
1363        //tfk_debug($result);
1364        // reload message headers if cached
1365        $cache_key = $this->mailbox.'.msg';
1366        if ($this->caching_enabled) {
1367            foreach ($msg_ids as $uid => $id) {
1368                if ($cached_headers = $this->get_cached_message($cache_key, $uid)) {
1369                    $this->remove_message_cache($cache_key, $id);
1370                    //$this->get_headers($uid);
1371                }
1372            }
1373
1374            // close and re-open connection
1375            // this prevents connection problems with Courier
1376            $this->reconnect();
1377        }
1378
1379        // set nr of messages that were flaged
1380        $count = count($msg_ids);
1381
1382        // clear message count cache
1383        if ($result && $flag=='SEEN') {
1384            $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
1385        }
1386        elseif ($result && $flag=='UNSEEN') {
1387            $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
1388        }
1389        elseif ($result && $flag=='DELETED') {
1390            $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
1391        }
1392        return $result;
1393    }
1394
1395
1396    // append a mail message (source) to a specific mailbox
1397    function save_message($mbox_name, &$message)
1398    {
1399        $mbox_name = stripslashes($mbox_name);
1400        $mailbox = $this->_mod_mailbox($mbox_name);
1401
1402        // make sure mailbox exists
1403        if (in_array($mailbox, $this->_list_mailboxes())) {
1404            $saved = iil_C_Append($this->conn, $mailbox, $message);
1405        }
1406        if ($saved) {
1407            // increase messagecount of the target mailbox
1408            $this->_set_messagecount($mailbox, 'ALL', 1);
1409        }
1410        return $saved;
1411    }
1412
1413
1414  // move a message from one mailbox to another
1415  function move_message($uids, $to_mbox, $from_mbox='')
1416    {
1417    $to_mbox = stripslashes($to_mbox);
1418    $from_mbox = stripslashes($from_mbox);
1419    $to_mbox = $this->_mod_mailbox($to_mbox);
1420    $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
1421
1422    // make sure mailbox exists
1423    if (!in_array($to_mbox, $this->_list_mailboxes()))
1424      {
1425      if (in_array(strtolower($to_mbox), $this->default_folders))
1426        $this->create_mailbox($to_mbox, TRUE);
1427      else
1428        return FALSE;
1429      }
1430
1431    // convert the list of uids to array
1432    $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1433
1434    // exit if no message uids are specified
1435    if (!is_array($a_uids))
1436      return false;
1437
1438    // convert uids to message ids
1439    $a_mids = array();
1440    foreach ($a_uids as $uid)
1441      $a_mids[] = $this->_uid2id($uid, $from_mbox);
1442
1443    $iil_move = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
1444    $moved = !($iil_move === false || $iil_move < 0);
1445
1446    // send expunge command in order to have the moved message
1447    // really deleted from the source mailbox
1448    if ($moved)
1449      {
1450      $this->_expunge($from_mbox, FALSE);
1451      $this->_clear_messagecount($from_mbox);
1452      $this->_clear_messagecount($to_mbox);
1453      }
1454
1455    // remove message ids from search set
1456    if ($moved && $this->search_set && $from_mbox == $this->mailbox)
1457      $this->search_set = array_diff($this->search_set, $a_mids);
1458
1459    // update cached message headers
1460    $cache_key = $from_mbox.'.msg';
1461    if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1462      {
1463      $start_index = 100000;
1464      foreach ($a_uids as $uid)
1465        {
1466        if (($index = array_search($uid, $a_cache_index)) !== FALSE)
1467          $start_index = min($index, $start_index);
1468        }
1469
1470      // clear cache from the lowest index on
1471      $this->clear_message_cache($cache_key, $start_index);
1472      }
1473
1474    return $moved;
1475    }
1476
1477
1478  // mark messages as deleted and expunge mailbox
1479  function delete_message($uids, $mbox_name='')
1480    {
1481    $mbox_name = stripslashes($mbox_name);
1482    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1483
1484    // convert the list of uids to array
1485    $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1486
1487    // exit if no message uids are specified
1488    if (!is_array($a_uids))
1489      return false;
1490
1491
1492    // convert uids to message ids
1493    $a_mids = array();
1494    foreach ($a_uids as $uid)
1495      $a_mids[] = $this->_uid2id($uid, $mailbox);
1496
1497    $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1498
1499    // send expunge command in order to have the deleted message
1500    // really deleted from the mailbox
1501    if ($deleted)
1502      {
1503      $this->_expunge($mailbox, FALSE);
1504      $this->_clear_messagecount($mailbox);
1505      }
1506
1507    // remove message ids from search set
1508    if ($moved && $this->search_set && $mailbox == $this->mailbox)
1509      $this->search_set = array_diff($this->search_set, $a_mids);
1510
1511    // remove deleted messages from cache
1512    $cache_key = $mailbox.'.msg';
1513    if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1514      {
1515      $start_index = 100000;
1516      foreach ($a_uids as $uid)
1517        {
1518        if (($index = array_search($uid, $a_cache_index)) !== FALSE)
1519          $start_index = min($index, $start_index);
1520        }
1521
1522      // clear cache from the lowest index on
1523      $this->clear_message_cache($cache_key, $start_index);
1524      }
1525
1526    return $deleted;
1527    }
1528
1529
1530  // clear all messages in a specific mailbox
1531  function clear_mailbox($mbox_name=NULL)
1532    {
1533    $mbox_name = stripslashes($mbox_name);
1534    $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1535    $msg_count = $this->_messagecount($mailbox, 'ALL');
1536
1537    if ($msg_count>0)
1538      {
1539      $cleared = iil_C_ClearFolder($this->conn, $mailbox);
1540
1541      // make sure the message count cache is cleared as well
1542      if ($cleared)
1543        {
1544        $this->clear_message_cache($mailbox.'.msg');
1545        $a_mailbox_cache = $this->get_cache('messagecount');
1546        unset($a_mailbox_cache[$mailbox]);
1547        $this->update_cache('messagecount', $a_mailbox_cache);
1548        }
1549
1550      return $cleared;
1551      }
1552    else
1553      return 0;
1554    }
1555
1556
1557  // send IMAP expunge command and clear cache
1558  function expunge($mbox_name='', $clear_cache=TRUE)
1559    {
1560    $mbox_name = stripslashes($mbox_name);
1561    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1562    return $this->_expunge($mailbox, $clear_cache);
1563    }
1564
1565
1566  // send IMAP expunge command and clear cache
1567  function _expunge($mailbox, $clear_cache=TRUE)
1568    {
1569    $result = iil_C_Expunge($this->conn, $mailbox);
1570
1571    if ($result>=0 && $clear_cache)
1572      {
1573      //$this->clear_message_cache($mailbox.'.msg');
1574      $this->_clear_messagecount($mailbox);
1575      }
1576
1577    return $result;
1578    }
1579
1580
1581  /* --------------------------------
1582   *        folder managment
1583   * --------------------------------*/
1584
1585
1586    /**
1587     * Get a list of all folders available on the IMAP server
1588     *
1589     * @param string IMAP root dir
1590     * @return array Inbdexed array with folder names
1591     */
1592    function list_unsubscribed($root='')
1593    {
1594        static $sa_unsubscribed;
1595
1596        if (is_array($sa_unsubscribed)) {
1597            return $sa_unsubscribed;
1598        }
1599        // retrieve list of folders from IMAP server
1600        $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1601
1602        // modify names with root dir
1603        foreach ($a_mboxes as $mbox_name) {
1604            $name = $this->_mod_mailbox($mbox_name, 'out');
1605            if (empty($name) === false) {
1606                $a_folders[] = $name;
1607            }
1608        }
1609
1610        // filter folders and sort them
1611        $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1612        return $sa_unsubscribed;
1613    }
1614
1615
1616  /**
1617   * Get quota
1618   * added by Nuny
1619   */
1620  function get_quota()
1621    {
1622    if ($this->get_capability('QUOTA'))
1623      return iil_C_GetQuota($this->conn);
1624
1625    return FALSE;
1626    }
1627
1628
1629  /**
1630   * subscribe to a specific mailbox(es)
1631   */
1632  function subscribe($mbox_name, $mode='subscribe')
1633    {
1634    if (is_array($mbox_name))
1635      $a_mboxes = $mbox_name;
1636    else if (is_string($mbox_name) && strlen($mbox_name))
1637      $a_mboxes = explode(',', $mbox_name);
1638
1639    // let this common function do the main work
1640    return $this->_change_subscription($a_mboxes, 'subscribe');
1641    }
1642
1643
1644  /**
1645   * unsubscribe mailboxes
1646   */
1647  function unsubscribe($mbox_name)
1648    {
1649    if (is_array($mbox_name))
1650      $a_mboxes = $mbox_name;
1651    else if (is_string($mbox_name) && strlen($mbox_name))
1652      $a_mboxes = explode(',', $mbox_name);
1653
1654    // let this common function do the main work
1655    return $this->_change_subscription($a_mboxes, 'unsubscribe');
1656    }
1657
1658
1659    /**
1660     * Create a new mailbox on the server and register it in local cache
1661     *
1662     * @param string  New mailbox name (as utf-7 string)
1663     * @param boolean True if the new mailbox should be subscribed
1664     * @param string  Name of the created mailbox, false on error
1665     */
1666    function create_mailbox($name, $subscribe=FALSE)
1667    {
1668        $result = FALSE;
1669
1670        // replace backslashes
1671        $name = preg_replace('/[\\\]+/', '-', $name);
1672
1673        // reduce mailbox name to 100 chars
1674        $name = substr($name, 0, 100);
1675
1676        $abs_name = $this->_mod_mailbox($name);
1677        $a_mailbox_cache = $this->get_cache('mailboxes');
1678
1679        if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array_nocase($abs_name, $a_mailbox_cache)))
1680            $result = iil_C_CreateFolder($this->conn, $abs_name);
1681
1682        // try to subscribe it
1683        if ($subscribe)
1684            $this->subscribe($name);
1685
1686        return $result ? $name : FALSE;
1687    }
1688
1689
1690  /**
1691   * Set a new name to an existing mailbox
1692   *
1693   * @param string Mailbox to rename (as utf-7 string)
1694   * @param string New mailbox name (as utf-7 string)
1695   * @param string Name of the renames mailbox, false on error
1696   */
1697  function rename_mailbox($mbox_name, $new_name)
1698    {
1699    $result = FALSE;
1700
1701    // replace backslashes
1702    $name = preg_replace('/[\\\]+/', '-', $new_name);
1703
1704    // encode mailbox name and reduce it to 100 chars
1705    $name = substr($new_name, 0, 100);
1706
1707    // make absolute path
1708    $mailbox = $this->_mod_mailbox($mbox_name);
1709    $abs_name = $this->_mod_mailbox($name);
1710
1711    // check if mailbox is subscribed
1712    $a_subscribed = $this->_list_mailboxes();
1713    $subscribed = in_array($mailbox, $a_subscribed);
1714
1715    // unsubscribe folder
1716    if ($subscribed)
1717      iil_C_UnSubscribe($this->conn, $mailbox);
1718
1719    if (strlen($abs_name))
1720      $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
1721
1722    // clear cache
1723    if ($result)
1724      {
1725      $this->clear_message_cache($mailbox.'.msg');
1726      $this->clear_cache('mailboxes');
1727      }
1728
1729    // try to subscribe it
1730    if ($result && $subscribed)
1731      iil_C_Subscribe($this->conn, $abs_name);
1732
1733    return $result ? $name : FALSE;
1734    }
1735
1736
1737  /**
1738   * remove mailboxes from server
1739   */
1740  function delete_mailbox($mbox_name)
1741    {
1742    $deleted = FALSE;
1743
1744    if (is_array($mbox_name))
1745      $a_mboxes = $mbox_name;
1746    else if (is_string($mbox_name) && strlen($mbox_name))
1747      $a_mboxes = explode(',', $mbox_name);
1748
1749    if (is_array($a_mboxes))
1750      foreach ($a_mboxes as $mbox_name)
1751        {
1752        $mailbox = $this->_mod_mailbox($mbox_name);
1753
1754        // unsubscribe mailbox before deleting
1755        iil_C_UnSubscribe($this->conn, $mailbox);
1756
1757        // send delete command to server
1758        $result = iil_C_DeleteFolder($this->conn, $mailbox);
1759        if ($result>=0)
1760          $deleted = TRUE;
1761        }
1762
1763    // clear mailboxlist cache
1764    if ($deleted)
1765      {
1766      $this->clear_message_cache($mailbox.'.msg');
1767      $this->clear_cache('mailboxes');
1768      }
1769
1770    return $deleted;
1771    }
1772
1773
1774  /**
1775   * Create all folders specified as default
1776   */
1777  function create_default_folders()
1778    {
1779    $a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*');
1780    $a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*');
1781
1782    // create default folders if they do not exist
1783    foreach ($this->default_folders as $folder)
1784      {
1785      $abs_name = $this->_mod_mailbox($folder);
1786      if (!in_array_nocase($abs_name, $a_subscribed))
1787        {
1788        if (!in_array_nocase($abs_name, $a_folders))
1789          $this->create_mailbox($folder, TRUE);
1790        else
1791          $this->subscribe($folder);
1792        }
1793      else if (!in_array_nocase($abs_name, $a_folders))
1794        {
1795        $this->create_mailbox($folder, FALSE);
1796        }
1797      }
1798    }
1799
1800
1801
1802  /* --------------------------------
1803   *   internal caching methods
1804   * --------------------------------*/
1805
1806
1807  function set_caching($set)
1808    {
1809    if ($set && is_object($this->db))
1810      $this->caching_enabled = TRUE;
1811    else
1812      $this->caching_enabled = FALSE;
1813    }
1814
1815
1816  function get_cache($key)
1817    {
1818    // read cache
1819    if (!isset($this->cache[$key]) && $this->caching_enabled)
1820      {
1821      $cache_data = $this->_read_cache_record('IMAP.'.$key);
1822      $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
1823      }
1824
1825    return $this->cache[$key];
1826    }
1827
1828
1829  function update_cache($key, $data)
1830    {
1831    $this->cache[$key] = $data;
1832    $this->cache_changed = TRUE;
1833    $this->cache_changes[$key] = TRUE;
1834    }
1835
1836
1837  function write_cache()
1838    {
1839    if ($this->caching_enabled && $this->cache_changed)
1840      {
1841      foreach ($this->cache as $key => $data)
1842        {
1843        if ($this->cache_changes[$key])
1844          $this->_write_cache_record('IMAP.'.$key, serialize($data));
1845        }
1846      }
1847    }
1848
1849
1850  function clear_cache($key=NULL)
1851    {
1852    if ($key===NULL)
1853      {
1854      foreach ($this->cache as $key => $data)
1855        $this->_clear_cache_record('IMAP.'.$key);
1856
1857      $this->cache = array();
1858      $this->cache_changed = FALSE;
1859      $this->cache_changes = array();
1860      }
1861    else
1862      {
1863      $this->_clear_cache_record('IMAP.'.$key);
1864      $this->cache_changes[$key] = FALSE;
1865      unset($this->cache[$key]);
1866      }
1867    }
1868
1869
1870
1871  function _read_cache_record($key)
1872    {
1873    $cache_data = FALSE;
1874
1875    if ($this->db)
1876      {
1877      // get cached data from DB
1878      $sql_result = $this->db->query(
1879        "SELECT cache_id, data
1880         FROM ". rc_main::get_table_name('cache')."
1881         WHERE  user_id=?
1882         AND    cache_key=?",
1883        $_SESSION['user_id'],
1884        $key);
1885
1886      if ($sql_arr = $this->db->fetch_assoc($sql_result))
1887        {
1888        $cache_data = $sql_arr['data'];
1889        $this->cache_keys[$key] = $sql_arr['cache_id'];
1890        }
1891      }
1892
1893    return $cache_data;
1894    }
1895
1896
1897  function _write_cache_record($key, $data)
1898    {
1899    if (!$this->db)
1900      return FALSE;
1901
1902    // check if we already have a cache entry for this key
1903    if (!isset($this->cache_keys[$key]))
1904      {
1905      $sql_result = $this->db->query(
1906        "SELECT cache_id
1907         FROM ".rc_main::get_table_name('cache')."
1908         WHERE  user_id=?
1909         AND    cache_key=?",
1910        $_SESSION['user_id'],
1911        $key);
1912
1913      if ($sql_arr = $this->db->fetch_assoc($sql_result))
1914        $this->cache_keys[$key] = $sql_arr['cache_id'];
1915      else
1916        $this->cache_keys[$key] = FALSE;
1917      }
1918
1919    // update existing cache record
1920    if ($this->cache_keys[$key])
1921      {
1922      $this->db->query(
1923        "UPDATE ".rc_main::get_table_name('cache')."
1924         SET    created=".$this->db->now().",
1925                data=?
1926         WHERE  user_id=?
1927         AND    cache_key=?",
1928        $data,
1929        $_SESSION['user_id'],
1930        $key);
1931      }
1932    // add new cache record
1933    else
1934      {
1935      $this->db->query(
1936        "INSERT INTO ". rc_main::get_table_name('cache')."
1937         (created, user_id, cache_key, data)
1938         VALUES (".$this->db->now().", ?, ?, ?)",
1939        $_SESSION['user_id'],
1940        $key,
1941        $data);
1942      }
1943    }
1944
1945
1946  function _clear_cache_record($key)
1947    {
1948    $this->db->query(
1949      "DELETE FROM ". rc_main::get_table_name('cache')."
1950       WHERE  user_id=?
1951       AND    cache_key=?",
1952      $_SESSION['user_id'],
1953      $key);
1954    }
1955
1956
1957
1958  /* --------------------------------
1959   *   message caching methods
1960   * --------------------------------*/
1961
1962
1963  // checks if the cache is up-to-date
1964  // return: -3 = off, -2 = incomplete, -1 = dirty
1965  function check_cache_status($mailbox, $cache_key)
1966    {
1967    if (!$this->caching_enabled)
1968      return -3;
1969
1970    $cache_index = $this->get_message_cache_index($cache_key, TRUE);
1971    $msg_count = $this->_messagecount($mailbox);
1972    $cache_count = count($cache_index);
1973
1974    // console("Cache check: $msg_count !== ".count($cache_index));
1975
1976    if ($cache_count==$msg_count)
1977      {
1978      // get highest index
1979      $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
1980      $cache_uid = array_pop($cache_index);
1981
1982      // uids of highest message matches -> cache seems OK
1983      if ($cache_uid == $header->uid)
1984        return 1;
1985
1986      // cache is dirty
1987      return -1;
1988      }
1989    // if cache count differs less than 10% report as dirty
1990    else if (abs($msg_count - $cache_count) < $msg_count/10)
1991      return -1;
1992    else
1993      return -2;
1994    }
1995
1996
1997
1998  function get_message_cache($key, $from, $to, $sort_field, $sort_order)
1999    {
2000    $cache_key = "$key:$from:$to:$sort_field:$sort_order";
2001    $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
2002
2003    if (!in_array($sort_field, $db_header_fields))
2004      $sort_field = 'idx';
2005
2006    if ($this->caching_enabled && !isset($this->cache[$cache_key]))
2007      {
2008      $this->cache[$cache_key] = array();
2009      $sql_result = $this->db->limitquery(
2010        "SELECT idx, uid, headers
2011         FROM ". rc_main::get_table_name('messages')."
2012         WHERE  user_id=?
2013         AND    cache_key=?
2014         ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
2015         strtoupper($sort_order),
2016        $from,
2017        $to-$from,
2018        $_SESSION['user_id'],
2019        $key);
2020
2021      while ($sql_arr = $this->db->fetch_assoc($sql_result))
2022        {
2023        $uid = $sql_arr['uid'];
2024        $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
2025        }
2026      }
2027
2028    return $this->cache[$cache_key];
2029    }
2030
2031
2032  function &get_cached_message($key, $uid, $struct=false)
2033    {
2034    if (!$this->caching_enabled)
2035      return FALSE;
2036
2037    $internal_key = '__single_msg';
2038    if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) ||
2039        ($struct && empty($this->cache[$internal_key][$uid]->structure))))
2040      {
2041      $sql_select = "idx, uid, headers" . ($struct ? ", structure" : '');
2042      $sql_result = $this->db->query(
2043        "SELECT $sql_select
2044         FROM ". rc_main::get_table_name('messages')."
2045         WHERE  user_id=?
2046         AND    cache_key=?
2047         AND    uid=?",
2048        $_SESSION['user_id'],
2049        $key,
2050        $uid);
2051
2052      if ($sql_arr = $this->db->fetch_assoc($sql_result))
2053        {
2054        $this->cache[$internal_key][$uid] = unserialize($sql_arr['headers']);
2055        if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure']))
2056          $this->cache[$internal_key][$uid]->structure = unserialize($sql_arr['structure']);
2057        }
2058      }
2059
2060    return $this->cache[$internal_key][$uid];
2061    }
2062
2063
2064  function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
2065    {
2066    static $sa_message_index = array();
2067
2068    // empty key -> empty array
2069    if (!$this->caching_enabled || empty($key))
2070      return array();
2071
2072    if (!empty($sa_message_index[$key]) && !$force)
2073      return $sa_message_index[$key];
2074
2075    $sa_message_index[$key] = array();
2076    $sql_result = $this->db->query(
2077      "SELECT idx, uid
2078       FROM ". rc_main::get_table_name('messages')."
2079       WHERE  user_id=?
2080       AND    cache_key=?
2081       ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
2082      $_SESSION['user_id'],
2083      $key);
2084
2085    while ($sql_arr = $this->db->fetch_assoc($sql_result))
2086      $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
2087
2088    return $sa_message_index[$key];
2089    }
2090
2091
2092  function add_message_cache($key, $index, $headers, $struct=null)
2093    {
2094    if (!$this->caching_enabled || empty($key) || !is_object($headers) || empty($headers->uid))
2095      return;
2096
2097    // check for an existing record (probly headers are cached but structure not)
2098    $sql_result = $this->db->query(
2099        "SELECT message_id
2100         FROM ". rc_main::get_table_name('messages')."
2101         WHERE  user_id=?
2102         AND    cache_key=?
2103         AND    uid=?
2104         AND    del<>1",
2105        $_SESSION['user_id'],
2106        $key,
2107        $headers->uid);
2108
2109    // update cache record
2110    if ($sql_arr = $this->db->fetch_assoc($sql_result))
2111      {
2112      $this->db->query(
2113        "UPDATE ". rc_main::get_table_name('messages')."
2114         SET   idx=?, headers=?, structure=?
2115         WHERE message_id=?",
2116        $index,
2117        serialize($headers),
2118        is_object($struct) ? serialize($struct) : NULL,
2119        $sql_arr['message_id']
2120        );
2121      }
2122    else  // insert new record
2123      {
2124      $this->db->query(
2125        "INSERT INTO ". rc_main::get_table_name('messages')."
2126         (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers, structure)
2127         VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?, ?)",
2128        $_SESSION['user_id'],
2129        $key,
2130        $index,
2131        $headers->uid,
2132        (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
2133        (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
2134        (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
2135        (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
2136        (int)$headers->size,
2137        serialize($headers),
2138        is_object($struct) ? serialize($struct) : NULL
2139        );
2140      }
2141    }
2142
2143
2144  function remove_message_cache($key, $index)
2145    {
2146    $this->db->query(
2147      "DELETE FROM ". rc_main::get_table_name('messages')."
2148       WHERE  user_id=?
2149       AND    cache_key=?
2150       AND    idx=?",
2151      $_SESSION['user_id'],
2152      $key,
2153      $index);
2154    }
2155
2156
2157  function clear_message_cache($key, $start_index=1)
2158    {
2159    $this->db->query(
2160      "DELETE FROM ". rc_main::get_table_name('messages')."
2161       WHERE  user_id=?
2162       AND    cache_key=?
2163       AND    idx>=?",
2164      $_SESSION['user_id'],
2165      $key,
2166      $start_index);
2167    }
2168
2169
2170
2171
2172  /* --------------------------------
2173   *   encoding/decoding methods
2174   * --------------------------------*/
2175
2176
2177  function decode_address_list($input, $max=null, $decode=true)
2178    {
2179    $a = $this->_parse_address_list($input, $decode);
2180    $out = array();
2181
2182    if (!is_array($a))
2183      return $out;
2184
2185    $c = count($a);
2186    $j = 0;
2187
2188    foreach ($a as $val)
2189      {
2190      $j++;
2191      $address = $val['address'];
2192      $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
2193      if ($name && $address && $name != $address)
2194        $string = sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address);
2195      else if ($address)
2196        $string = $address;
2197      else if ($name)
2198        $string = $name;
2199
2200      $out[$j] = array('name' => $name,
2201                       'mailto' => $address,
2202                       'string' => $string);
2203
2204      if ($max && $j==$max)
2205        break;
2206      }
2207
2208    return $out;
2209    }
2210
2211
2212  function decode_header($input, $remove_quotes=FALSE)
2213    {
2214    $str = $this->decode_mime_string((string)$input);
2215    if ($str{0}=='"' && $remove_quotes)
2216      $str = str_replace('"', '', $str);
2217
2218    return $str;
2219    }
2220
2221
2222  /**
2223   * Decode a mime-encoded string to internal charset
2224   *
2225   * @access static
2226   */
2227  function decode_mime_string($input, $fallback=null)
2228    {
2229    $out = '';
2230
2231    $pos = strpos($input, '=?');
2232    if ($pos !== false)
2233      {
2234      $out = substr($input, 0, $pos);
2235
2236      $end_cs_pos = strpos($input, "?", $pos+2);
2237      $end_en_pos = strpos($input, "?", $end_cs_pos+1);
2238      $end_pos = strpos($input, "?=", $end_en_pos+1);
2239
2240      $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
2241      $rest = substr($input, $end_pos+2);
2242
2243      $out .= rcube_imap::_decode_mime_string_part($encstr);
2244      $out .= rcube_imap::decode_mime_string($rest, $fallback);
2245
2246      return $out;
2247      }
2248
2249    // no encoding information, use fallback
2250    return rc_main::rcube_charset_convert($input, !empty($fallback) ? $fallback : 'ISO-8859-1');
2251    }
2252
2253
2254  /**
2255   * Decode a part of a mime-encoded string
2256   *
2257   * @access static
2258   */
2259  function _decode_mime_string_part($str)
2260    {
2261    $a = explode('?', $str);
2262    $count = count($a);
2263
2264    // should be in format "charset?encoding?base64_string"
2265    if ($count >= 3)
2266      {
2267      for ($i=2; $i<$count; $i++)
2268        $rest.=$a[$i];
2269
2270      if (($a[1]=="B")||($a[1]=="b"))
2271        $rest = base64_decode($rest);
2272      else if (($a[1]=="Q")||($a[1]=="q"))
2273        {
2274        $rest = str_replace("_", " ", $rest);
2275        $rest = quoted_printable_decode($rest);
2276        }
2277
2278      return rc_main::rcube_charset_convert($rest, $a[0]);
2279      }
2280    else
2281      return $str;    // we dont' know what to do with this
2282    }
2283
2284
2285  function mime_decode($input, $encoding='7bit')
2286    {
2287    switch (strtolower($encoding))
2288      {
2289      case '7bit':
2290        return $input;
2291        break;
2292
2293      case 'quoted-printable':
2294        return quoted_printable_decode($input);
2295        break;
2296
2297      case 'base64':
2298        return base64_decode($input);
2299        break;
2300
2301      default:
2302        return $input;
2303      }
2304    }
2305
2306
2307  function mime_encode($input, $encoding='7bit')
2308    {
2309    switch ($encoding)
2310      {
2311      case 'quoted-printable':
2312        return quoted_printable_encode($input);
2313        break;
2314
2315      case 'base64':
2316        return base64_encode($input);
2317        break;
2318
2319      default:
2320        return $input;
2321      }
2322    }
2323
2324
2325  // convert body chars according to the ctype_parameters
2326  function charset_decode($body, $ctype_param)
2327    {
2328    if (is_array($ctype_param) && !empty($ctype_param['charset']))
2329      return rc_main::rcube_charset_convert($body, $ctype_param['charset']);
2330
2331    // defaults to what is specified in the class header
2332    return rc_main::rcube_charset_convert($body,  'ISO-8859-1');
2333    }
2334
2335
2336
2337
2338  /* --------------------------------
2339   *         private methods
2340   * --------------------------------*/
2341
2342
2343  function _mod_mailbox($mbox_name, $mode='in')
2344    {
2345    if (empty($mbox_name) || (!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX')
2346      return $mbox_name;
2347
2348    if (!empty($this->root_dir) && $mode=='in')
2349      $mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
2350    else if (strlen($this->root_dir) && $mode=='out')
2351      $mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
2352
2353    return $mbox_name;
2354    }
2355
2356
2357  // sort mailboxes first by default folders and then in alphabethical order
2358  function _sort_mailbox_list($a_folders)
2359    {
2360    $a_out = $a_defaults = array();
2361
2362    // find default folders and skip folders starting with '.'
2363    foreach($a_folders as $i => $folder)
2364      {
2365      if ($folder{0}=='.')
2366        continue;
2367
2368      if (($p = array_search(strtolower($folder), $this->default_folders_lc))!==FALSE)
2369        $a_defaults[$p] = $folder;
2370      else
2371        $a_out[] = $folder;
2372      }
2373
2374    sort($a_out);
2375    ksort($a_defaults);
2376
2377    return array_merge($a_defaults, $a_out);
2378    }
2379
2380  function get_id($uid, $mbox_name=NULL)
2381    {
2382      return $this->_uid2id($uid, $this->_mod_mailbox($mbox_name));
2383    }
2384
2385  function get_uid($id,$mbox_name=NULL)
2386    {
2387      return $this->_id2uid($id, $this->_mod_mailbox($mbox_name));
2388    }
2389
2390  function _uid2id($uid, $mbox_name=NULL)
2391    {
2392    if (!$mbox_name)
2393      $mbox_name = $this->mailbox;
2394
2395    if (!isset($this->uid_id_map[$mbox_name][$uid]))
2396      $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
2397
2398    return $this->uid_id_map[$mbox_name][$uid];
2399    }
2400
2401  function _id2uid($id, $mbox_name=NULL)
2402    {
2403    if (!$mbox_name)
2404      $mbox_name = $this->mailbox;
2405
2406    return iil_C_ID2UID($this->conn, $mbox_name, $id);
2407    }
2408
2409
2410  // parse string or array of server capabilities and put them in internal array
2411  function _parse_capability($caps)
2412    {
2413    if (!is_array($caps))
2414      $cap_arr = explode(' ', $caps);
2415    else
2416      $cap_arr = $caps;
2417
2418    foreach ($cap_arr as $cap)
2419      {
2420      if ($cap=='CAPABILITY')
2421        continue;
2422
2423      if (strpos($cap, '=')>0)
2424        {
2425        list($key, $value) = explode('=', $cap);
2426        if (!is_array($this->capabilities[$key]))
2427          $this->capabilities[$key] = array();
2428
2429        $this->capabilities[$key][] = $value;
2430        }
2431      else
2432        $this->capabilities[$cap] = TRUE;
2433      }
2434    }
2435
2436
2437  // subscribe/unsubscribe a list of mailboxes and update local cache
2438  function _change_subscription($a_mboxes, $mode)
2439    {
2440    $updated = FALSE;
2441
2442    if (is_array($a_mboxes))
2443      foreach ($a_mboxes as $i => $mbox_name)
2444        {
2445        $mailbox = $this->_mod_mailbox($mbox_name);
2446        $a_mboxes[$i] = $mailbox;
2447
2448        if ($mode=='subscribe')
2449          $result = iil_C_Subscribe($this->conn, $mailbox);
2450        else if ($mode=='unsubscribe')
2451          $result = iil_C_UnSubscribe($this->conn, $mailbox);
2452
2453        if ($result>=0)
2454          $updated = TRUE;
2455        }
2456
2457    // get cached mailbox list
2458    if ($updated)
2459      {
2460      $a_mailbox_cache = $this->get_cache('mailboxes');
2461      if (!is_array($a_mailbox_cache))
2462        return $updated;
2463
2464      // modify cached list
2465      if ($mode=='subscribe')
2466        $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
2467      else if ($mode=='unsubscribe')
2468        $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
2469
2470      // write mailboxlist to cache
2471      $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
2472      }
2473
2474    return $updated;
2475    }
2476
2477
2478  // increde/decrese messagecount for a specific mailbox
2479  function _set_messagecount($mbox_name, $mode, $increment)
2480    {
2481    $a_mailbox_cache = FALSE;
2482    $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
2483    $mode = strtoupper($mode);
2484
2485    $a_mailbox_cache = $this->get_cache('messagecount');
2486
2487    if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
2488      return FALSE;
2489
2490    // add incremental value to messagecount
2491    $a_mailbox_cache[$mailbox][$mode] += $increment;
2492
2493    // there's something wrong, delete from cache
2494    if ($a_mailbox_cache[$mailbox][$mode] < 0)
2495      unset($a_mailbox_cache[$mailbox][$mode]);
2496
2497    // write back to cache
2498    $this->update_cache('messagecount', $a_mailbox_cache);
2499
2500    return TRUE;
2501    }
2502
2503
2504  // remove messagecount of a specific mailbox from cache
2505  function _clear_messagecount($mbox_name='')
2506    {
2507    $a_mailbox_cache = FALSE;
2508    $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
2509
2510    $a_mailbox_cache = $this->get_cache('messagecount');
2511
2512    if (is_array($a_mailbox_cache[$mailbox]))
2513      {
2514      unset($a_mailbox_cache[$mailbox]);
2515      $this->update_cache('messagecount', $a_mailbox_cache);
2516      }
2517    }
2518
2519
2520  // split RFC822 header string into an associative array
2521  function _parse_headers($headers)
2522    {
2523    $a_headers = array();
2524    $lines = explode("\n", $headers);
2525    $c = count($lines);
2526    for ($i=0; $i<$c; $i++)
2527      {
2528      if ($p = strpos($lines[$i], ': '))
2529        {
2530        $field = strtolower(substr($lines[$i], 0, $p));
2531        $value = trim(substr($lines[$i], $p+1));
2532        if (!empty($value))
2533          $a_headers[$field] = $value;
2534        }
2535      }
2536
2537    return $a_headers;
2538    }
2539
2540
2541  function _parse_address_list($str, $decode=true)
2542    {
2543    // remove any newlines and carriage returns before
2544    $a = $this->_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str));
2545    $result = array();
2546
2547    foreach ($a as $key => $val)
2548      {
2549      $val = preg_replace("/([\"\w])</", "$1 <", $val);
2550      $sub_a = $this->_explode_quoted_string(' ', $decode ? $this->decode_header($val) : $val);
2551      $result[$key]['name'] = '';
2552
2553      foreach ($sub_a as $k => $v)
2554        {
2555        if (strpos($v, '@') > 0)
2556          $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
2557        else
2558          $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
2559        }
2560
2561      if (empty($result[$key]['name']))
2562        $result[$key]['name'] = $result[$key]['address'];
2563      }
2564
2565    return $result;
2566    }
2567
2568
2569  function _explode_quoted_string($delimiter, $string)
2570    {
2571    $result = array();
2572    $strlen = strlen($string);
2573    for ($q=$p=$i=0; $i < $strlen; $i++)
2574    {
2575      if ($string{$i} == "\"" && $string{$i-1} != "\\")
2576        $q = $q ? false : true;
2577      else if (!$q && preg_match("/$delimiter/", $string{$i}))
2578      {
2579        $result[] = substr($string, $p, $i - $p);
2580        $p = $i + 1;
2581      }
2582    }
2583
2584    $result[] = substr($string, $p);
2585    return $result;
2586    }
2587  }
2588
2589
2590/**
2591 * Class representing a message part
2592 */
2593class rcube_message_part
2594{
2595  var $mime_id = '';
2596  var $ctype_primary = 'text';
2597  var $ctype_secondary = 'plain';
2598  var $mimetype = 'text/plain';
2599  var $disposition = '';
2600  var $filename = '';
2601  var $encoding = '8bit';
2602  var $charset = '';
2603  var $size = 0;
2604  var $headers = array();
2605  var $d_parameters = array();
2606  var $ctype_parameters = array();
2607
2608}
2609
2610
2611/**
2612 * rcube_header_sorter
2613 *
2614 * Class for sorting an array of iilBasicHeader objects in a predetermined order.
2615 *
2616 * @author Eric Stadtherr
2617 */
2618class rcube_header_sorter
2619{
2620   var $sequence_numbers = array();
2621
2622   /**
2623    * set the predetermined sort order.
2624    *
2625    * @param array $seqnums numerically indexed array of IMAP message sequence numbers
2626    */
2627   function set_sequence_numbers($seqnums)
2628   {
2629      $this->sequence_numbers = $seqnums;
2630   }
2631
2632   /**
2633    * sort the array of header objects
2634    *
2635    * @param array $headers array of iilBasicHeader objects indexed by UID
2636    */
2637   function sort_headers(&$headers)
2638   {
2639      /*
2640       * uksort would work if the keys were the sequence number, but unfortunately
2641       * the keys are the UIDs.  We'll use uasort instead and dereference the value
2642       * to get the sequence number (in the "id" field).
2643       *
2644       * uksort($headers, array($this, "compare_seqnums"));
2645       */
2646       uasort($headers, array($this, "compare_seqnums"));
2647   }
2648
2649   /**
2650    * get the position of a message sequence number in my sequence_numbers array
2651    *
2652    * @param integer $seqnum message sequence number contained in sequence_numbers
2653    */
2654   function position_of($seqnum)
2655   {
2656      $c = count($this->sequence_numbers);
2657      for ($pos = 0; $pos <= $c; $pos++)
2658      {
2659         if ($this->sequence_numbers[$pos] == $seqnum)
2660            return $pos;
2661      }
2662      return -1;
2663   }
2664
2665   /**
2666    * Sort method called by uasort()
2667    */
2668   function compare_seqnums($a, $b)
2669   {
2670      // First get the sequence number from the header object (the 'id' field).
2671      $seqa = $a->id;
2672      $seqb = $b->id;
2673
2674      // then find each sequence number in my ordered list
2675      $posa = $this->position_of($seqa);
2676      $posb = $this->position_of($seqb);
2677
2678      // return the relative position as the comparison value
2679      $ret = $posa - $posb;
2680      return $ret;
2681   }
2682}
2683
2684
2685/**
2686 * Add quoted-printable encoding to a given string
2687 *
2688 * @param string  $input      string to encode
2689 * @param int     $line_max   add new line after this number of characters
2690 * @param boolena $space_conf true if spaces should be converted into =20
2691 * @return encoded string
2692 */
2693function quoted_printable_encode($input, $line_max=76, $space_conv=false)
2694  {
2695  $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
2696  $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
2697  $eol = "\r\n";
2698  $escape = "=";
2699  $output = "";
2700
2701  while( list(, $line) = each($lines))
2702    {
2703    //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
2704    $linlen = strlen($line);
2705    $newline = "";
2706    for($i = 0; $i < $linlen; $i++)
2707      {
2708      $c = substr( $line, $i, 1 );
2709      $dec = ord( $c );
2710      if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
2711        {
2712        $c = "=2E";
2713        }
2714      if ( $dec == 32 )
2715        {
2716        if ( $i == ( $linlen - 1 ) ) // convert space at eol only
2717          {
2718          $c = "=20";
2719          }
2720        else if ( $space_conv )
2721          {
2722          $c = "=20";
2723          }
2724        }
2725      else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
2726        {
2727        $h2 = floor($dec/16);
2728        $h1 = floor($dec%16);
2729        $c = $escape.$hex["$h2"].$hex["$h1"];
2730        }
2731
2732      if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
2733        {
2734        $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
2735        $newline = "";
2736        // check if newline first character will be point or not
2737        if ( $dec == 46 )
2738          {
2739          $c = "=2E";
2740          }
2741        }
2742      $newline .= $c;
2743      } // end of for
2744    $output .= $newline.$eol;
2745    } // end of while
2746
2747  return trim($output);
2748  }
2749
2750
2751?>
Note: See TracBrowser for help on using the repository browser.