thomascube
2009-02-08 21b160f38c98bf41ebc843e7639b5b1af588b489
program/include/rcube_imap.php
@@ -5,7 +5,7 @@
 | program/include/rcube_imap.php                                        |
 |                                                                       |
 | This file is part of the RoundCube Webmail client                     |
 | Copyright (C) 2005-2008, RoundCube Dev. - Switzerland                 |
 | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland                 |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 | PURPOSE:                                                              |
@@ -26,6 +26,7 @@
 */
require_once('lib/imap.inc');
require_once('lib/mime.inc');
require_once('lib/tnef_decoder.inc');
/**
@@ -66,6 +67,7 @@
  var $search_sort_field = '';  
  var $debug_level = 1;
  var $error_code = 0;
  var $options = array('imap' => 'check');
  /**
@@ -90,7 +92,7 @@
   * @return boolean  TRUE on success, FALSE on failure
   * @access public
   */
  function connect($host, $user, $pass, $port=143, $use_ssl=null, $auth_type=null)
  function connect($host, $user, $pass, $port=143, $use_ssl=null)
    {
    global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
    
@@ -107,7 +109,7 @@
    $ICL_PORT = $port;
    $IMAP_USE_INTERNAL_DATE = false;
    $this->conn = iil_Connect($host, $user, $pass, array('imap' => $auth_type ? $auth_type : 'check'));
    $this->conn = iil_Connect($host, $user, $pass, $this->options);
    $this->host = $host;
    $this->user = $user;
    $this->pass = $pass;
@@ -172,6 +174,13 @@
      iil_C_Select($this->conn, $this->mailbox);
    }
  /**
   * Set options to be used in iil_Connect()
   */
  function set_options($opt)
  {
    $this->options = array_merge($this->options, (array)$opt);
  }
  /**
   * Set a root folder for the IMAP connection.
@@ -188,6 +197,7 @@
      $root = substr($root, 0, -1);
    $this->root_dir = $root;
    $this->options['rootdir'] = $root;
    
    if (empty($this->delimiter))
      $this->get_hierarchy_delimiter();
@@ -294,7 +304,7 @@
      $msgs = split(',', $msgs);
      
    $this->search_string = $str;
    $this->search_set = (array)$msgs;
    $this->search_set = $msgs;
    $this->search_charset = $charset;
    $this->search_sort_field = $sort_field;
    }
@@ -345,8 +355,9 @@
   */
  function check_permflag($flag)
    {
    $flagsmap = $GLOBALS['IMAP_FLAGS'];
    return (($imap_flag = $flagsmap[strtoupper($flag)]) && in_array_nocase($imap_flag, $this->conn->permanentflags));
    $flag = strtoupper($flag);
    $imap_flag = $GLOBALS['IMAP_FLAGS'][$flag];
    return (in_array_nocase($imap_flag, $this->conn->permanentflags));
    }
@@ -674,11 +685,11 @@
      }
    else { // SEARCH searching result, need sorting
      $cnt = count($msgs);
      if ($cnt > 300) { // experimantal best result
      if ($cnt > 300 && $cnt > $this->page_size) { // experimantal value for best result
        // use memory less expensive (and quick) method for big result set
   $a_index = $this->message_index($mailbox, $this->sort_field, $this->sort_order);
   $a_index = $this->message_index('', $this->sort_field, $this->sort_order);
        // get messages uids for one page...
        $msgs = array_slice(array_keys($a_index), $start_msg, min($cnt-$start_msg, $this->page_size));
        $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size));
   // ...and fetch headers
        $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
@@ -811,7 +822,7 @@
    // 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->cache[$key] = array();
      
      if ($this->get_capability('sort'))
        {
@@ -832,7 +843,7 @@
        else if ($this->sort_order=="DESC")
          arsort($a_index);
        $this->cache[$key] = $a_index;
        $this->cache[$key] = array_keys($a_index);
   }
    }
@@ -848,9 +859,8 @@
    if ($cache_status>0)
      {
      $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
      return array_values($a_index);
      return array_keys($a_index);
      }
    // fetch complete message index
    $msg_count = $this->_messagecount($mailbox);
@@ -870,7 +880,7 @@
      else if ($this->sort_order=="DESC")
        arsort($a_index);
        
      $this->cache[$key] = $a_index;
      $this->cache[$key] = array_keys($a_index);
      }
    return $this->cache[$key];
@@ -1039,9 +1049,10 @@
   * @param int     Message ID
   * @param string  Mailbox to read from 
   * @param boolean True if $id is the message UID
   * @param boolean True if we need also BODYSTRUCTURE in headers
   * @return object Message headers representation
   */
  function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
  function get_headers($id, $mbox_name=NULL, $is_uid=TRUE, $bodystr=FALSE)
    {
    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
    $uid = $is_uid ? $id : $this->_id2uid($id);
@@ -1050,7 +1061,7 @@
    if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid)))
      return $headers;
    $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid);
    $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr);
    // write headers cache
    if ($headers)
@@ -1070,9 +1081,10 @@
   * an object structure similar to the one generated by PEAR::Mail_mimeDecode
   *
   * @param int Message UID to fetch
   * @param string Message BODYSTRUCTURE string (optional)
   * @return object rcube_message_part Message part tree or False on failure
   */
  function &get_structure($uid)
  function &get_structure($uid, $structure_str='')
    {
    $cache_key = $this->mailbox.'.msg';
    $headers = &$this->get_cached_message($cache_key, $uid, true);
@@ -1087,7 +1099,8 @@
      return FALSE;
    }
    $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
    if (!$structure_str)
      $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
    $structure = iml_GetRawStructureArray($structure_str);
    $struct = false;
@@ -1122,7 +1135,7 @@
   *
   * @access private
   */
  function &_structure_part($part, $count=0, $parent='')
  function &_structure_part($part, $count=0, $parent='', $raw_headers=null)
    {
    $struct = new rcube_message_part;
    $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
@@ -1142,11 +1155,25 @@
          
      $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
      // build parts list for headers pre-fetching
      for ($i=0, $count=0; $i<count($part); $i++)
        if (is_array($part[$i]) && count($part[$i]) > 3)
     // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
     if (strtolower($part[$i][0]) == 'message' ||
       (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL'))) {
       $part_headers[] = $struct->mime_id ? $struct->mime_id.'.'.$i+1 : $i+1;
       }
      // pre-fetch headers of all parts (in one command for better performance)
      if ($part_headers)
        $part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, $this->_msg_id, $part_headers);
      $struct->parts = array();
      for ($i=0, $count=0; $i<count($part); $i++)
        if (is_array($part[$i]) && count($part[$i]) > 3)
          $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id);
          $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id,
      $part_headers[$struct->mime_id ? $struck->mime_id.'.'.$i+1 : $i+1]);
      return $struct;
      }
    
@@ -1211,8 +1238,9 @@
    
    // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
    if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) {
      $part_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id);
      $struct->headers = $this->_parse_headers($part_headers) + $struct->headers;
      if (empty($raw_headers))
        $raw_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id);
      $struct->headers = $this->_parse_headers($raw_headers) + $struct->headers;
    }
    if ($struct->ctype_primary=='message') {
@@ -1221,7 +1249,7 @@
    }
    // normalize filename property
    $this->_set_part_filename($struct);
    $this->_set_part_filename($struct, $raw_headers);
    return $struct;
    }
@@ -1232,8 +1260,9 @@
   *
   * @access private
   * @param  object rcube_message_part Part object
   * @param  string Part's raw headers
   */
  function _set_part_filename(&$part)
  function _set_part_filename(&$part, $headers=null)
    {
    if (!empty($part->d_parameters['filename']))
      $filename_mime = $part->d_parameters['filename'];
@@ -1251,10 +1280,9 @@
      }
      // some servers (eg. dovecot-1.x) have no support for parameter value continuations
      // we must fetch and parse headers "manually"
      //TODO: fetching headers for a second time is not effecient, this code should be moved somewhere earlier --tensor
      if ($i<2) {
        // TODO: fetch only Content-Type/Content-Disposition header
        $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
   if (!$headers)
          $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
        $filename_mime = '';
        $i = 0;
        while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
@@ -1270,7 +1298,8 @@
        $i++;
      }
      if ($i<2) {
        $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
   if (!$headers)
          $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
        $filename_encoded = '';
        $i = 0; $matches = array();
        while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
@@ -1286,7 +1315,8 @@
        $i++;
      }
      if ($i<2) {
        $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
   if (!$headers)
          $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
        $filename_mime = '';
        $i = 0; $matches = array();
        while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
@@ -1302,7 +1332,8 @@
        $i++;
      }
      if ($i<2) {
        $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
   if (!$headers)
          $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
        $filename_encoded = '';
        $i = 0; $matches = array();
        while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
@@ -1364,6 +1395,8 @@
      }
      
    // TODO: Add caching for message parts
    if (!$part) $part = 'TEXT';
    if ($print)
      {
@@ -1436,10 +1469,7 @@
    if (!($msg_id = $this->_uid2id($uid)))
      return FALSE;
    $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
    $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
    return $body;
    return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id);
    }
@@ -1470,8 +1500,6 @@
    if (!($msg_id = $this->_uid2id($uid)))
      return FALSE;
    print iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
    flush();
    iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2);
    }
@@ -1545,7 +1573,6 @@
   */
  function save_message($mbox_name, &$message)
    {
    $mbox_name = stripslashes($mbox_name);
    $mailbox = $this->_mod_mailbox($mbox_name);
    // make sure mailbox exists
@@ -1572,13 +1599,11 @@
   */
  function move_message($uids, $to_mbox, $from_mbox='')
    {
    $to_mbox_in = stripslashes($to_mbox);
    $from_mbox = stripslashes($from_mbox);
    $to_mbox = $this->_mod_mailbox($to_mbox_in);
    $to_mbox = $this->_mod_mailbox($to_mbox);
    $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
    // make sure mailbox exists
    if (!in_array($to_mbox, $this->_list_mailboxes()))
    if ($to_mbox != 'INBOX' && !in_array($to_mbox, $this->_list_mailboxes()))
      {
      if (in_array($to_mbox_in, $this->default_folders))
        $this->create_mailbox($to_mbox_in, TRUE);
@@ -1649,7 +1674,6 @@
   */
  function delete_message($uids, $mbox_name='')
    {
    $mbox_name = stripslashes($mbox_name);
    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
    // convert the list of uids to array
@@ -1706,7 +1730,6 @@
   */
  function clear_mailbox($mbox_name=NULL)
    {
    $mbox_name = stripslashes($mbox_name);
    $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
    $msg_count = $this->_messagecount($mailbox, 'ALL');
    
@@ -1739,7 +1762,6 @@
   */
  function expunge($mbox_name='', $clear_cache=TRUE)
    {
    $mbox_name = stripslashes($mbox_name);
    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
    return $this->_expunge($mailbox, $clear_cache);
    }
@@ -1858,9 +1880,6 @@
    {
    $result = FALSE;
    
    // replace backslashes
    $name = preg_replace('/[\\\]+/', '-', $name);
    // reduce mailbox name to 100 chars
    $name = substr($name, 0, 100);
@@ -1889,9 +1908,6 @@
    {
    $result = FALSE;
    // replace backslashes
    $name = preg_replace('/[\\\]+/', '-', $new_name);
    // encode mailbox name and reduce it to 100 chars
    $name = substr($new_name, 0, 100);
@@ -2149,8 +2165,7 @@
      {
      $this->db->query(
        "UPDATE ".get_table_name('cache')."
         SET    created=".$this->db->now().",
                data=?
         SET    created=". $this->db->now().", data=?
         WHERE  user_id=?
         AND    cache_key=?",
        $data,
@@ -2337,7 +2352,7 @@
    {
    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;
@@ -2478,6 +2493,40 @@
    
    return $out;
    }
  /**
   * Decode a Microsoft Outlook TNEF part (winmail.dat)
   *
   * @param object rcube_message_part Message part to decode
   * @param string UID of the message
   * @return array List of rcube_message_parts extracted from windmail.dat
   */
  function tnef_decode(&$part, $uid)
  {
    if (!isset($part->body))
      $part->body = $this->get_message_part($uid, $part->mime_id, $part);
    $pid = 0;
    $tnef_parts = array();
    $tnef_arr = tnef_decode($part->body);
    foreach ($tnef_arr as $winatt) {
      $tpart = new rcube_message_part;
      $tpart->filename = $winatt["name"];
      $tpart->encoding = 'stream';
      $tpart->ctype_primary = $winatt["type0"];
      $tpart->ctype_secondary = $winatt["type1"];
      $tpart->mimetype = strtolower($winatt["type0"] . "/" . $winatt["type1"]);
      $tpart->mime_id = "winmail." . $part->mime_id . ".$pid";
      $tpart->size = $winatt["size"];
      $tpart->body = $winatt['stream'];
      $tnef_parts[] = $tpart;
      $pid++;
    }
    return $tnef_parts;
  }
  /**
@@ -2500,12 +2549,13 @@
  /**
   * Decode a mime-encoded string to internal charset
   *
   * @param string  Header value
   * @param string  Fallback charset if none specified
   * @param string $input    Header value
   * @param string $fallback Fallback charset if none specified
   *
   * @return string Decoded string
   * @static
   */
  function decode_mime_string($input, $fallback=null)
  public static function decode_mime_string($input, $fallback=null)
    {
    // Initialize variable
    $out = '';
@@ -2714,31 +2764,42 @@
        $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7'));
      }
    // sort folders and place defaults on the top
    asort($folders, SORT_LOCALE_STRING);
    ksort($a_defaults);
    $folders = array_merge($a_defaults, array_keys($folders));
    // finally we must rebuild the list to move 
    // subfolders of default folders to their place...
    // ...also do this for the rest of folders because
    // asort() is not properly sorting case sensitive names
    // set the type of folder name variable (#1485527)
    while (list($key, $folder) = each($folders)) {
      // set the type of folder name variable (#1485527)
      $a_out[] = (string) $folder;
      unset($folders[$key]);
      foreach ($folders as $idx => $f) {
   if (strpos($f, $folder.$delimiter) === 0) {
         $a_out[] = (string) $f;
     unset($folders[$idx]);
     }
        }
      reset($folders);
      $this->_rsort($folder, $delimiter, $folders, $a_out);
      }
    return $a_out;
    }
  /**
   * @access private
   */
  function _rsort($folder, $delimiter, &$list, &$out)
    {
      while (list($key, $name) = each($list)) {
   if (strpos($name, $folder.$delimiter) === 0) {
     // set the type of folder name variable (#1485527)
         $out[] = (string) $name;
     unset($list[$key]);
     $this->_rsort($name, $delimiter, $list, $out);
     }
        }
      reset($list);
    }
  /**
   * @access private
@@ -2899,7 +2960,7 @@
    // remove any newlines and carriage returns before
    $a = $this->_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str));
    $result = array();
    foreach ($a as $key => $val)
      {
      $val = preg_replace("/([\"\w])</", "$1 <", $val);
@@ -2908,14 +2969,17 @@
      foreach ($sub_a as $k => $v)
        {
        if (strpos($v, '@') > 0)
          $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
   // use angle brackets in regexp to not handle names with @ sign
        if (preg_match('/^<\S+@\S+>$/', $v))
          $result[$key]['address'] = trim($v, '<>');
        else
          $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
        }
        
      if (empty($result[$key]['name']))
        $result[$key]['name'] = $result[$key]['address'];        
      elseif (empty($result[$key]['address']))
        $result[$key]['address'] = $result[$key]['name'];
      }
    
    return $result;