From e70d6ea64e711096af36b1234f8545b870ea5f45 Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Sat, 05 Apr 2008 08:49:21 -0400
Subject: [PATCH] Apply changes from trunk to 0.1-stable

---
 program/include/rcube_imap.inc |  251 +++++++++++++++++++++++++++++++++++---------------
 1 files changed, 175 insertions(+), 76 deletions(-)

diff --git a/program/include/rcube_imap.inc b/program/include/rcube_imap.inc
index 0cfda15..9a59485 100644
--- a/program/include/rcube_imap.inc
+++ b/program/include/rcube_imap.inc
@@ -5,7 +5,7 @@
  | program/include/rcube_imap.inc                                        |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005-2006, RoundCube Dev. - Switzerland                 |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -35,7 +35,7 @@
  *
  * @package    Mail
  * @author     Thomas Bruederli <roundcube@gmail.com>
- * @version    1.36
+ * @version    1.40
  * @link       http://ilohamail.org
  */
 class rcube_imap
@@ -51,6 +51,7 @@
   var $sort_order = 'DESC';
   var $delimiter = NULL;
   var $caching_enabled = FALSE;
+  var $default_charset = 'ISO-8859-1';
   var $default_folders = array('INBOX');
   var $default_folders_lc = array('inbox');
   var $cache = array();
@@ -65,6 +66,7 @@
   var $search_string = '';
   var $search_charset = '';
   var $debug_level = 1;
+  var $error_code = 0;
 
 
   /**
@@ -96,17 +98,17 @@
    * @param  string   Username for IMAP account
    * @param  string   Password for IMAP account
    * @param  number   Port to connect to
-   * @param  boolean  Use SSL connection
+   * @param  string   SSL schema (either ssl or tls) or null if plain connection
    * @return boolean  TRUE on success, FALSE on failure
    * @access public
    */
-  function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
+  function connect($host, $user, $pass, $port=143, $use_ssl=null)
     {
     global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
     
     // check for Open-SSL support in PHP build
     if ($use_ssl && in_array('openssl', get_loaded_extensions()))
-      $ICL_SSL = TRUE;
+      $ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
     else if ($use_ssl)
       {
       raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
@@ -131,6 +133,7 @@
     // write error log
     else if (!$this->conn && $GLOBALS['iil_error'])
       {
+      $this->error_code = $GLOBALS['iil_errornum'];
       raise_error(array('code' => 403,
                        'type' => 'imap',
                        'message' => $GLOBALS['iil_error']), TRUE, FALSE);
@@ -198,6 +201,20 @@
     
     if (empty($this->delimiter))
       $this->get_hierarchy_delimiter();
+    }
+
+
+  /**
+   * Set default message charset
+   *
+   * This will be used for message decoding if a charset specification is not available
+   *
+   * @param  string   Charset string
+   * @access public
+   */
+  function set_charset($cs)
+    {
+    $this->default_charset = $ch;
     }
 
 
@@ -287,7 +304,7 @@
       
     $this->search_subject = $subject;
     $this->search_string = $str;
-    $this->search_set = is_array($msgs) ? $msgs : NULL;
+    $this->search_set = (array)$msgs;
     $this->search_charset = $charset;
     }
 
@@ -439,8 +456,8 @@
       $mailbox = $this->mailbox;
       
     // count search set
-    if ($this->search_set && $mailbox == $this->mailbox && $mode == 'ALL')
-      return count($this->search_set);
+    if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force)
+      return count((array)$this->search_set);
 
     $a_mailbox_cache = $this->get_cache('messagecount');
     
@@ -522,13 +539,10 @@
       return array();
 
     // use saved message set
-    if ($this->search_set && $mailbox == $this->mailbox)
+    if ($this->search_string && $mailbox == $this->mailbox)
       return $this->_list_header_set($mailbox, $this->search_set, $page, $sort_field, $sort_order);
 
-    if ($sort_field!=NULL)
-      $this->sort_field = $sort_field;
-    if ($sort_order!=NULL)
-      $this->sort_order = strtoupper($sort_order);
+    $this->_set_sort_order($sort_field, $sort_order);
 
     $max = $this->_messagecount($mailbox);
     $start_msg = ($this->list_page-1) * $this->page_size;
@@ -645,10 +659,7 @@
     if (!strlen($mailbox) || empty($msgs))
       return array();
 
-    if ($sort_field!=NULL)
-      $this->sort_field = $sort_field;
-    if ($sort_order!=NULL)
-      $this->sort_order = strtoupper($sort_order);
+    $this->_set_sort_order($sort_field, $sort_order);
 
     $max = count($msgs);
     $start_msg = ($this->list_page-1) * $this->page_size;
@@ -762,13 +773,20 @@
    */
   function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
     {
-    if ($sort_field!=NULL)
-      $this->sort_field = $sort_field;
-    if ($sort_order!=NULL)
-      $this->sort_order = strtoupper($sort_order);
+    $this->_set_sort_order($sort_field, $sort_order);
 
     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
-    $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
+    $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi";
+
+    // we have a saved search result. get index from there
+    if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox)
+    {
+      $this->cache[$key] = $a_msg_headers = array();
+      $this->_fetch_headers($mailbox, join(',', $this->search_set), $a_msg_headers, NULL);
+
+      foreach (iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order) as $i => $msg)
+        $this->cache[$key][] = $msg->uid;
+    }
 
     // have stored it in RAM
     if (isset($this->cache[$key]))
@@ -941,6 +959,20 @@
       
     return $this->get_search_set();
     }
+  
+  
+  /**
+   * Check if the given message ID is part of the current search set
+   *
+   * @return boolean True on match or if no search request is stored
+   */
+  function in_searchset($msgid)
+  {
+    if (!empty($this->search_string))
+      return in_array("$msgid", (array)$this->search_set, true);
+    else
+      return true;
+  }
 
 
   /**
@@ -965,8 +997,8 @@
     // write headers cache
     if ($headers)
       {
-      if ($is_uid)
-        $this->uid_id_map[$mbox_name][$uid] = $headers->id;
+      if ($headers->uid && $headers->id)
+        $this->uid_id_map[$mailbox][$headers->uid] = $headers->id;
 
       $this->add_message_cache($mailbox.'.msg', $headers->id, $headers);
       }
@@ -1003,7 +1035,7 @@
     if (!empty($structure))
       {
       $this->_msg_id = $msg_id;
-      $headers = $this->get_headers($msg_id, NULL, FALSE);
+      $headers = $this->get_headers($uid);
       
       $struct = &$this->_structure_part($structure);
       $struct->headers = get_object_vars($headers);
@@ -1128,12 +1160,16 @@
       }
       
     // normalize filename property
-    if (!empty($struct->d_parameters['filename']))
-      $struct->filename = $this->decode_mime_string($struct->d_parameters['filename']);
-    else if (!empty($struct->ctype_parameters['name']))
-      $struct->filename = $this->decode_mime_string($struct->ctype_parameters['name']);
+    if ($filename_mime = $struct->d_parameters['filename'] ? $struct->d_parameters['filename'] : $struct->ctype_parameters['name'])
+      $struct->filename = rcube_imap::decode_mime_string($filename_mime, $this->default_charset);
+    else if ($filename_encoded = $struct->d_parameters['filename*'] ? $struct->d_parameters['filename*'] : $struct->ctype_parameters['name*'])
+    {
+      // decode filename according to RFC 2231, Section 4
+      list($filename_charset,, $filename_urlencoded) = split('\'', $filename_encoded);
+      $struct->filename = rcube_charset_convert(urldecode($filename_urlencoded), $filename_charset);
+    }
     else if (!empty($struct->headers['content-description']))
-      $struct->filename = $this->decode_mime_string($struct->headers['content-description']);
+      $struct->filename = rcube_imap::decode_mime_string($struct->headers['content-description'], $this->default_charset);
       
     return $struct;
     }
@@ -1199,23 +1235,30 @@
 
     if ($print)
       {
-      iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, ($o_part->encoding=='base64'?3:2));
-      $body = TRUE;
+      $mode = $o_part->encoding == 'base64' ? 3 : ($o_part->encoding == 'quoted-printable' ? 1 : 2);
+      $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, $mode);
+      
+      // we have to decode the part manually before printing
+      if ($mode == 1)
+        {
+        echo $this->mime_decode($body, $o_part->encoding);
+        $body = true;
+        }
       }
     else
       {
       $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 1);
 
       // decode part body
-      if ($o_part->encoding=='base64' || $o_part->encoding=='quoted-printable')
+      if ($o_part->encoding)
         $body = $this->mime_decode($body, $o_part->encoding);
 
       // convert charset (if text or message part)
       if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message')
         {
-        // assume ISO-8859-1 if no charset specified
+        // assume default if no charset specified
         if (empty($o_part->charset))
-          $o_part->charset = 'ISO-8859-1';
+          $o_part->charset = $this->default_charset;
 
         $body = rcube_charset_convert($body, $o_part->charset);
         }
@@ -1276,7 +1319,7 @@
    * Set message flag to one or several messages
    *
    * @param mixed  Message UIDs as array or as comma-separated string
-   * @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT
+   * @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
    * @return boolean True on success, False on failure
    */
   function set_flag($uids, $flag)
@@ -1374,7 +1417,7 @@
     // make sure mailbox exists
     if (!in_array($to_mbox, $this->_list_mailboxes()))
       {
-      if (in_array(strtolower($to_mbox), $this->default_folders))
+      if (in_array($to_mbox, $this->default_folders))
         $this->create_mailbox($to_mbox, TRUE);
       else
         return FALSE;
@@ -1544,7 +1587,7 @@
 
     if ($result>=0 && $clear_cache)
       {
-      //$this->clear_message_cache($mailbox.'.msg');
+      $this->clear_message_cache($mailbox.'.msg');
       $this->_clear_messagecount($mailbox);
       }
       
@@ -1605,16 +1648,14 @@
   /**
    * Subscribe to a specific mailbox(es)
    *
-   * @param string Mailbox name(s)
+   * @param array Mailbox name(s)
    * @return boolean True on success
    */ 
-  function subscribe($mbox_name)
+  function subscribe($a_mboxes)
     {
-    if (is_array($mbox_name))
-      $a_mboxes = $mbox_name;
-    else if (is_string($mbox_name) && strlen($mbox_name))
-      $a_mboxes = explode(',', $mbox_name);
-    
+    if (!is_array($a_mboxes))
+      $a_mboxes = array($a_mboxes);
+
     // let this common function do the main work
     return $this->_change_subscription($a_mboxes, 'subscribe');
     }
@@ -1623,15 +1664,13 @@
   /**
    * Unsubscribe mailboxes
    *
-   * @param string Mailbox name(s)
+   * @param array Mailbox name(s)
    * @return boolean True on success
    */
-  function unsubscribe($mbox_name)
+  function unsubscribe($a_mboxes)
     {
-    if (is_array($mbox_name))
-      $a_mboxes = $mbox_name;
-    else if (is_string($mbox_name) && strlen($mbox_name))
-      $a_mboxes = explode(',', $mbox_name);
+    if (!is_array($a_mboxes))
+      $a_mboxes = array($a_mboxes);
 
     // let this common function do the main work
     return $this->_change_subscription($a_mboxes, 'unsubscribe');
@@ -1658,11 +1697,11 @@
     $abs_name = $this->_mod_mailbox($name);
     $a_mailbox_cache = $this->get_cache('mailboxes');
 
-    if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array_nocase($abs_name, $a_mailbox_cache)))
+    if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
       $result = iil_C_CreateFolder($this->conn, $abs_name);
 
     // try to subscribe it
-    if ($subscribe)
+    if ($result && $subscribe)
       $this->subscribe($name);
 
     return $result ? $name : FALSE;
@@ -1701,9 +1740,19 @@
     if (strlen($abs_name))
       $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
 
-    // clear cache
     if ($result)
       {
+      $delm = $this->get_hierarchy_delimiter();
+      
+      // check if mailbox children are subscribed
+      foreach ($a_subscribed as $c_subscribed)
+        if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed))
+          {
+          iil_C_UnSubscribe($this->conn, $c_subscribed);
+          iil_C_Subscribe($this->conn, preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed));
+          }
+
+      // clear cache
       $this->clear_message_cache($mailbox.'.msg');
       $this->clear_cache('mailboxes');      
       }
@@ -1731,6 +1780,8 @@
     else if (is_string($mbox_name) && strlen($mbox_name))
       $a_mboxes = explode(',', $mbox_name);
 
+    $all_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
+
     if (is_array($a_mboxes))
       foreach ($a_mboxes as $mbox_name)
         {
@@ -1743,6 +1794,19 @@
         $result = iil_C_DeleteFolder($this->conn, $mailbox);
         if ($result>=0)
           $deleted = TRUE;
+
+        foreach ($all_mboxes as $c_mbox)
+          {
+          $regex = preg_quote($mailbox . $this->delimiter, '/');
+          $regex = '/^' . $regex . '/';
+          if (preg_match($regex, $c_mbox))
+            {
+            iil_C_UnSubscribe($this->conn, $c_mbox);
+            $result = iil_C_DeleteFolder($this->conn, $c_mbox);
+            if ($result>=0)
+              $deleted = TRUE;
+            }
+          }
         }
 
     // clear mailboxlist cache
@@ -1768,17 +1832,10 @@
     foreach ($this->default_folders as $folder)
       {
       $abs_name = $this->_mod_mailbox($folder);
-      if (!in_array_nocase($abs_name, $a_subscribed))
-        {
-        if (!in_array_nocase($abs_name, $a_folders))
-          $this->create_mailbox($folder, TRUE);
-        else
-          $this->subscribe($folder);
-        }
-      else if (!in_array_nocase($abs_name, $a_folders))
-        {
-        $this->create_mailbox($folder, FALSE);
-        }
+      if (!in_array_nocase($abs_name, $a_folders))
+        $this->create_mailbox($folder, TRUE);
+      else if (!in_array_nocase($abs_name, $a_subscribed))
+        $this->subscribe($folder);
       }
     }
 
@@ -2028,6 +2085,10 @@
         {
         $uid = $sql_arr['uid'];
         $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
+        
+        // featch headers if unserialize failed
+        if (empty($this->cache[$cache_key][$uid]))
+          $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true);
         }
       }
       
@@ -2039,10 +2100,8 @@
    */
   function &get_cached_message($key, $uid, $struct=false)
     {
-    if (!$this->caching_enabled)
-      return FALSE;
-
     $internal_key = '__single_msg';
+    
     if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) ||
         ($struct && empty($this->cache[$internal_key][$uid]->structure))))
       {
@@ -2103,9 +2162,17 @@
    */
   function add_message_cache($key, $index, $headers, $struct=null)
     {
-    if (!$this->caching_enabled || empty($key) || !is_object($headers) || empty($headers->uid))
+    if (empty($key) || !is_object($headers) || empty($headers->uid))
+        return;
+    
+    // add to internal (fast) cache
+    $this->cache['__single_msg'][$headers->uid] = $headers;
+    $this->cache['__single_msg'][$headers->uid]->structure = $struct;
+    
+    // no further caching
+    if (!$this->caching_enabled)
       return;
-      
+    
     // check for an existing record (probly headers are cached but structure not)
     $sql_result = $this->db->query(
         "SELECT message_id
@@ -2157,6 +2224,9 @@
    */
   function remove_message_cache($key, $index)
     {
+    if (!$this->caching_enabled)
+      return;
+    
     $this->db->query(
       "DELETE FROM ".get_table_name('messages')."
        WHERE  user_id=?
@@ -2172,6 +2242,9 @@
    */
   function clear_message_cache($key, $start_index=1)
     {
+    if (!$this->caching_enabled)
+      return;
+    
     $this->db->query(
       "DELETE FROM ".get_table_name('messages')."
        WHERE  user_id=?
@@ -2201,6 +2274,8 @@
     {
     $a = $this->_parse_address_list($input, $decode);
     $out = array();
+    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
+    $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
     
     if (!is_array($a))
       return $out;
@@ -2214,7 +2289,7 @@
       $address = $val['address'];
       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
       if ($name && $address && $name != $address)
-        $string = sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address);
+        $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
       else if ($address)
         $string = $address;
       else if ($name)
@@ -2241,7 +2316,7 @@
    */
   function decode_header($input, $remove_quotes=FALSE)
     {
-    $str = $this->decode_mime_string((string)$input);
+    $str = rcube_imap::decode_mime_string((string)$input, $this->default_charset);
     if ($str{0}=='"' && $remove_quotes)
       $str = str_replace('"', '', $str);
     
@@ -2264,6 +2339,10 @@
     $pos = strpos($input, '=?');
     if ($pos !== false)
       {
+      // rfc: all line breaks or other characters not found in the Base64 Alphabet must be ignored by decoding software
+      // delete all blanks between MIME-lines, differently we can receive unnecessary blanks and broken utf-8 symbols
+      $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
+
       $out = substr($input, 0, $pos);
   
       $end_cs_pos = strpos($input, "?", $pos+2);
@@ -2358,7 +2437,7 @@
       return rcube_charset_convert($body, $ctype_param['charset']);
 
     // defaults to what is specified in the class header
-    return rcube_charset_convert($body,  'ISO-8859-1');
+    return rcube_charset_convert($body,  $this->default_charset);
     }
 
 
@@ -2412,6 +2491,17 @@
     return $mbox_name;
     }
 
+  /**
+   * Validate the given input and save to local properties
+   * @access private
+   */
+  function _set_sort_order($sort_field, $sort_order)
+  {
+    if ($sort_field != null)
+      $this->sort_field = asciiwords($sort_field);
+    if ($sort_order != null)
+      $this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
+  }
 
   /**
    * Sort mailboxes first by default folders and then in alphabethical order
@@ -2427,13 +2517,13 @@
       if ($folder{0}=='.')
         continue;
 
-      if (($p = array_search(strtolower($folder), $this->default_folders_lc))!==FALSE)
+      if (($p = array_search(strtolower($folder), $this->default_folders_lc)) !== false && !$a_defaults[$p])
         $a_defaults[$p] = $folder;
       else
         $a_out[] = $folder;
       }
 
-    sort($a_out);
+    natcasesort($a_out);
     ksort($a_defaults);
     
     return array_merge($a_defaults, $a_out);
@@ -2461,7 +2551,16 @@
     if (!$mbox_name)
       $mbox_name = $this->mailbox;
       
-    return iil_C_ID2UID($this->conn, $mbox_name, $id);
+    $index = array_flip((array)$this->uid_id_map[$mbox_name]);
+    if (isset($index[$id]))
+      $uid = $index[$id];
+    else
+      {
+      $uid = iil_C_ID2UID($this->conn, $mbox_name, $id);
+      $this->uid_id_map[$mbox_name][$uid] = $id;
+      }
+    
+    return $uid;
     }
 
 

--
Gitblit v1.9.1