diff options
Diffstat (limited to 'program/include')
| -rwxr-xr-x | program/include/iniset.php | 2 | ||||
| -rw-r--r-- | program/include/main.inc | 178 | ||||
| -rw-r--r-- | program/include/rcmail.php | 32 | ||||
| -rw-r--r-- | program/include/rcube_imap.php | 327 | ||||
| -rw-r--r-- | program/include/rcube_imap_generic.php | 2695 | ||||
| -rw-r--r-- | program/include/rcube_message.php | 6 | ||||
| -rwxr-xr-x | program/include/rcube_template.php | 20 | ||||
| -rw-r--r-- | program/include/rcube_user.php | 8 | 
8 files changed, 1844 insertions, 1424 deletions
diff --git a/program/include/iniset.php b/program/include/iniset.php index a48ca9edf..8ccef8a7d 100755 --- a/program/include/iniset.php +++ b/program/include/iniset.php @@ -36,7 +36,7 @@ foreach ($crit_opts as $optname => $optval) {  }  // application constants -define('RCMAIL_VERSION', '0.5-beta'); +define('RCMAIL_VERSION', '0.5-rc');  define('RCMAIL_CHARSET', 'UTF-8');  define('JS_OBJECT_NAME', 'rcmail');  define('RCMAIL_START', microtime(true)); diff --git a/program/include/main.inc b/program/include/main.inc index 7ea4ae20f..b61f8aea2 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -1193,21 +1193,33 @@ function rcmail_log_login()    if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)      return; -  $address = $_SERVER['REMOTE_ADDR']; -  // append the NGINX X-Real-IP header, if set -  if (!empty($_SERVER['HTTP_X_REAL_IP'])) { -    $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP']; -  } -  // append the X-Forwarded-For header, if set -  if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { -    $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR']; -  } +  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s', +    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip())); +} -  if (!empty($remote_ip)) -    $address .= '(' . implode(',', $remote_ip) . ')'; -  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s', -    $RCMAIL->user->get_username(), $RCMAIL->user->ID, $address)); +/** + * Returns remote IP address and forwarded addresses if found + * + * @return string Remote IP address(es) + */ +function rcmail_remote_ip() +{ +    $address = $_SERVER['REMOTE_ADDR']; + +    // append the NGINX X-Real-IP header, if set +    if (!empty($_SERVER['HTTP_X_REAL_IP'])) { +        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP']; +    } +    // append the X-Forwarded-For header, if set +    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { +        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR']; +    } + +    if (!empty($remote_ip)) +        $address .= '(' . implode(',', $remote_ip) . ')'; + +    return $address;  } @@ -1219,7 +1231,7 @@ function rcube_timer()  {    return microtime(true);  } -   +  /**   * @access private @@ -1314,8 +1326,13 @@ function rcmail_mailbox_select($p = array())    $p += array('maxlength' => 100, 'realnames' => false);    $a_mailboxes = array(); -   -  foreach ($RCMAIL->imap->list_mailboxes() as $folder) + +  if ($p['unsubscribed']) +    $list = $RCMAIL->imap->list_unsubscribed(); +  else +    $list = $RCMAIL->imap->list_mailboxes(); + +  foreach ($list as $folder)      if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))        rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter()); @@ -1551,6 +1568,97 @@ function rcmail_localize_foldername($name)  } +function rcmail_quota_display($attrib) +{ +  global $OUTPUT; + +  if (!$attrib['id']) +    $attrib['id'] = 'rcmquotadisplay'; + +  if(isset($attrib['display'])) +    $_SESSION['quota_display'] = $attrib['display']; + +  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']); + +  $quota = rcmail_quota_content($attrib); + +  $OUTPUT->add_script('$(document).ready(function(){ +	rcmail.set_quota('.json_serialize($quota).')});', 'foot'); + +  return html::span($attrib, ''); +} + + +function rcmail_quota_content($attrib=NULL) +{ +  global $RCMAIL; + +  $quota = $RCMAIL->imap->get_quota(); +  $quota = $RCMAIL->plugins->exec_hook('quota', $quota); + +  $quota_result = (array) $quota; +  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; + +  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) { +    $quota_result['title'] = rcube_label('unlimited'); +    $quota_result['percent'] = 0; +  } +  else if ($quota['total']) { +    if (!isset($quota['percent'])) +      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100)); + +    $title = sprintf('%s / %s (%.0f%%)', +        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024), +        $quota_result['percent']); + +    $quota_result['title'] = $title; + +    if ($attrib['width']) +      $quota_result['width'] = $attrib['width']; +    if ($attrib['height']) +      $quota_result['height']	= $attrib['height']; +  } +  else { +    $quota_result['title'] = rcube_label('unknown'); +    $quota_result['percent'] = 0; +  } + +  return $quota_result; +} + + +/** + * Outputs error message according to server error/response codes + * + * @param string Fallback message label + * @param string Fallback message label arguments + * + * @return void + */ +function rcmail_display_server_error($fallback=null, $fallback_args=null) +{ +    global $RCMAIL; + +    $err_code = $RCMAIL->imap->get_error_code(); +    $res_code = $RCMAIL->imap->get_response_code(); + +    if ($res_code == rcube_imap::NOPERM) { +        $RCMAIL->output->show_message('errornoperm', 'error'); +    } +    else if ($res_code == rcube_imap::READONLY) { +        $RCMAIL->output->show_message('errorreadonly', 'error'); +    } +    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) { +        $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str)); +    } +    else if ($fallback) { +        $RCMAIL->output->show_message($fallback, 'error', $fallback_args); +    } + +    return true; +} + +  /**   * Output HTML editor scripts   * @@ -1583,6 +1691,43 @@ function rcube_html_editor($mode='')  /** + * Replaces TinyMCE's emoticon images with plain-text representation + * + * @param string HTML content + * @return string HTML content + */ +function rcmail_replace_emoticons($html) +{ +  $emoticons = array( +    '8-)' => 'smiley-cool', +    ':-#' => 'smiley-foot-in-mouth', +    ':-*' => 'smiley-kiss', +    ':-X' => 'smiley-sealed', +    ':-P' => 'smiley-tongue-out', +    ':-@' => 'smiley-yell', +    ":'(" => 'smiley-cry', +    ':-(' => 'smiley-frown', +    ':-D' => 'smiley-laughing', +    ':-)' => 'smiley-smile', +    ':-S' => 'smiley-undecided', +    ':-$' => 'smiley-embarassed', +    'O:-)' => 'smiley-innocent', +    ':-|' => 'smiley-money-mouth', +    ':-O' => 'smiley-surprised', +    ';-)' => 'smiley-wink', +  ); + +  foreach ($emoticons as $idx => $file) { +    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" /> +    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i'; +    $replace[] = $idx; +  } + +  return preg_replace($search, $replace, $html); +} + + +/**   * Check if working in SSL mode   *   * @param integer HTTPS port number @@ -1817,3 +1962,4 @@ function log_bug($arg_arr)          flush();      }  } + diff --git a/program/include/rcmail.php b/program/include/rcmail.php index e76b1420a..0eecd8ddb 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -678,10 +678,16 @@ class rcmail          $username .= '@'.rcube_parse_host($config['username_domain']);      } +    // Convert username to lowercase. If IMAP backend +    // is case-insensitive we need to store always the same username (#1487113) +    if ($config['login_lc']) { +      $username = mb_strtolower($username); +    } +      // try to resolve email address from virtuser table -    if (strpos($username, '@')) -      if ($virtuser = rcube_user::email2user($username)) -        $username = $virtuser; +    if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) { +      $username = $virtuser; +    }      // Here we need IDNA ASCII      // Only rcube_contacts class is using domain names in Unicode @@ -704,8 +710,14 @@ class rcmail      if (!($imap_login = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl))) {        // try with lowercase        $username_lc = mb_strtolower($username); -      if ($username_lc != $username && ($imap_login = $this->imap->connect($host, $username_lc, $pass, $imap_port, $imap_ssl))) -        $username = $username_lc; +      if ($username_lc != $username) { +        // try to find user record again -> overwrite username +        if (!$user && ($user = rcube_user::query($username_lc, $host))) +          $username_lc = $user->data['username']; + +        if ($imap_login = $this->imap->connect($host, $username_lc, $pass, $imap_port, $imap_ssl)) +          $username = $username_lc; +      }      }      // exit if IMAP login failed @@ -1203,8 +1215,14 @@ class rcmail      if (function_exists('mcrypt_module_open') &&          ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))      { -      $iv = substr($cipher, 0, mcrypt_enc_get_iv_size($td)); -      $cipher = substr($cipher, mcrypt_enc_get_iv_size($td)); +      $iv_size = mcrypt_enc_get_iv_size($td); +      $iv = substr($cipher, 0, $iv_size); + +      // session corruption? (#1485970) +      if (strlen($iv) < $iv_size) +        return ''; + +      $cipher = substr($cipher, $iv_size);        mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);        $clear = mdecrypt_generic($td, $cipher);        mcrypt_generic_deinit($td); diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 212e8865e..5715459a8 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -100,6 +100,16 @@ class rcube_imap          'RETURN-PATH',      ); +    const UNKNOWN       = 0; +    const NOPERM        = 1; +    const READONLY      = 2; +    const TRYCREATE     = 3; +    const INUSE         = 4; +    const OVERQUOTA     = 5; +    const ALREADYEXISTS = 6; +    const NONEXISTENT   = 7; +    const CONTACTADMIN  = 8; +      /**       * Object constructor @@ -156,21 +166,20 @@ class rcube_imap          $this->ssl  = $use_ssl;          if ($this->conn->connected()) { -            // print trace messages -            if ($this->conn->message && ($this->debug_level & 8)) { -                console($this->conn->message); -            }              // get namespace and delimiter              $this->set_env(); -              return true;          }          // write error log          else if ($this->conn->error) { -            if ($pass && $user) +            if ($pass && $user) { +                $message = sprintf("Login failed for %s from %s. %s", +                    $user, rcmail_remote_ip(), $this->conn->error); +                  raise_error(array('code' => 403, 'type' => 'imap',                      'file' => __FILE__, 'line' => __LINE__, -                    'message' => $this->conn->error), true, false); +                    'message' => $message), true, false); +            }          }          return false; @@ -185,7 +194,7 @@ class rcube_imap       */      function close()      { -        $this->conn->close(); +        $this->conn->closeConnection();          $this->write_cache();      } @@ -198,11 +207,11 @@ class rcube_imap       */      function reconnect()      { -        $this->close(); -        $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl); +        $this->conn->closeConnection(); +        $connected = $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);          // issue SELECT command to restore connection status -        if ($this->mailbox) +        if ($connected && strlen($this->mailbox))              $this->conn->select($this->mailbox);      } @@ -225,7 +234,51 @@ class rcube_imap       */      function get_error_str()      { -        return ($this->conn) ? $this->conn->error : ''; +        return ($this->conn) ? $this->conn->error : null; +    } + + +    /** +     * Returns code of last command response +     * +     * @return int Response code +     */ +    function get_response_code() +    { +        if (!$this->conn) +            return self::UNKNOWN; + +        switch ($this->conn->resultcode) { +            case 'NOPERM': +                return self::NOPERM; +            case 'READ-ONLY': +                return self::READONLY; +            case 'TRYCREATE': +                return self::TRYCREATE; +            case 'INUSE': +                return self::INUSE; +            case 'OVERQUOTA': +                return self::OVERQUOTA; +            case 'ALREADYEXISTS': +                return self::ALREADYEXISTS; +            case 'NONEXISTENT': +                return self::NONEXISTENT; +            case 'CONTACTADMIN': +                return self::CONTACTADMIN; +            default: +                return self::UNKNOWN; +        } +    } + + +    /** +     * Returns last command response +     * +     * @return string Response +     */ +    function get_response_str() +    { +        return ($this->conn) ? $this->conn->result : null;      } @@ -300,9 +353,9 @@ class rcube_imap       * @param  string $mailbox Mailbox/Folder name       * @access public       */ -    function select_mailbox($mailbox) +    function select_mailbox($mailbox=null)      { -        $mailbox = $this->mod_mailbox($mailbox); +        $mailbox = strlen($mailbox) ? $this->mod_mailbox($mailbox) : $this->mailbox;          $selected = $this->conn->select($mailbox); @@ -493,56 +546,56 @@ class rcube_imap          $imap_shared    = $config->get('imap_ns_shared');          $imap_delimiter = $config->get('imap_delimiter'); -        if ($imap_delimiter) { -            $this->delimiter = $imap_delimiter; -        } -          if (!$this->conn)              return;          $ns = $this->conn->getNamespace(); -        // NAMESPACE supported +        // Set namespaces (NAMESPACE supported)          if (is_array($ns)) {              $this->namespace = $ns; - -            if (empty($this->delimiter)) -                $this->delimiter = $ns['personal'][0][1]; -            if (empty($this->delimiter)) -                $this->delimiter = $this->conn->getHierarchyDelimiter(); -            if (empty($this->delimiter)) -                $this->delimiter = '/';          } -        // not supported, get namespace from config -        else if ($imap_personal !== null || $imap_shared !== null || $imap_other !== null) { -            if (empty($this->delimiter)) -                $this->delimiter = $this->conn->getHierarchyDelimiter(); -            if (empty($this->delimiter)) -                $this->delimiter = '/'; - +        else {              $this->namespace = array(                  'personal' => NULL,                  'other'    => NULL,                  'shared'   => NULL,              ); +        } -            if ($imap_personal !== null) { -                foreach ((array)$imap_personal as $dir) { -                    $this->namespace['personal'][] = array($dir, $this->delimiter); -                } +        if ($imap_delimiter) { +            $this->delimiter = $imap_delimiter; +        } +        if (empty($this->delimiter)) { +            $this->delimiter = $this->namespace['personal'][0][1]; +        } +        if (empty($this->delimiter)) { +            $this->delimiter = $this->conn->getHierarchyDelimiter(); +        } +        if (empty($this->delimiter)) { +            $this->delimiter = '/'; +        } + +        // Overwrite namespaces +        if ($imap_personal !== null) { +            $this->namespace['personal'] = NULL; +            foreach ((array)$imap_personal as $dir) { +                $this->namespace['personal'][] = array($dir, $this->delimiter);              } -            if ($imap_other !== null) { -                foreach ((array)$imap_other as $dir) { -                    if ($dir) { -                        $this->namespace['other'][] = array($dir, $this->delimiter); -                    } +        } +        if ($imap_other !== null) { +            $this->namespace['other'] = NULL; +            foreach ((array)$imap_other as $dir) { +                if ($dir) { +                    $this->namespace['other'][] = array($dir, $this->delimiter);                  }              } -            if ($imap_shared !== null) { -                foreach ((array)$imap_shared as $dir) { -                    if ($dir) { -                        $this->namespace['shared'][] = array($dir, $this->delimiter); -                    } +        } +        if ($imap_shared !== null) { +            $this->namespace['shared'] = NULL; +            foreach ((array)$imap_shared as $dir) { +                if ($dir) { +                    $this->namespace['shared'][] = array($dir, $this->delimiter);                  }              }          } @@ -617,7 +670,7 @@ class rcube_imap          }          // RECENT count is fetched a bit different          else if ($mode == 'RECENT') { -            $count = $this->conn->checkForRecent($mailbox); +            $count = $this->conn->countRecent($mailbox);          }          // use SEARCH for message counting          else if ($this->skip_deleted) { @@ -2376,20 +2429,21 @@ class rcube_imap          // TODO: Add caching for message parts -        if (!$part) $part = 'TEXT'; +        if (!$part) { +            $part = 'TEXT'; +        }          $body = $this->conn->handlePartBody($this->mailbox, $uid, true, $part,              $o_part->encoding, $print, $fp); -        if ($fp || $print) +        if ($fp || $print) {              return true; +        } -        // convert charset (if text or message part) -        if ($body && ($o_part->ctype_primary == 'text' || $o_part->ctype_primary == 'message')) { -            // assume default if no charset specified -            if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii') -                $o_part->charset = $this->default_charset; - +        // convert charset (if text or message part) and part's charset is specified +        if ($body && $o_part->charset +            && preg_match('/^(text|message)$/', $o_part->ctype_primary) +        ) {              $body = rcube_charset_convert($body, $o_part->charset);          } @@ -2553,6 +2607,9 @@ class rcube_imap          $to_mbox = $this->mod_mailbox($to_mbox);          $from_mbox = strlen($from_mbox) ? $this->mod_mailbox($from_mbox) : $this->mailbox; +        if ($to_mbox === $from_mbox) +            return false; +          list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox);          // exit if no message uids are specified @@ -2771,7 +2828,23 @@ class rcube_imap          else              $a_uids = NULL; -        $result = $this->conn->expunge($mailbox, $a_uids); +        // force mailbox selection and check if mailbox is writeable +        // to prevent a situation when CLOSE is executed on closed +        // or EXPUNGE on read-only mailbox +        $result = $this->conn->select($mailbox); +        if (!$result) { +            return false; +        } +        if (!$this->conn->data['READ-WRITE']) { +            $this->conn->setError(rcube_imap_generic::ERROR_READONLY, "Mailbox is read-only"); +            return false; +        } + +        // CLOSE(+SELECT) should be faster than EXPUNGE +        if (empty($a_uids) || $a_uids == '1:*') +            $result = $this->conn->close(); +        else +            $result = $this->conn->expunge($mailbox, $a_uids);          if ($result && $clear_cache) {              $this->clear_message_cache($mailbox.'.msg'); @@ -3009,6 +3082,26 @@ class rcube_imap      /** +     * Get mailbox size (size of all messages in a mailbox) +     * +     * @param string $name Mailbox name +     * @return int Mailbox size in bytes, False on error +     */ +    function get_mailbox_size($name) +    { +        $name = $this->mod_mailbox($name); + +        // @TODO: could we try to use QUOTA here? +        $result = $this->conn->fetchHeaderIndex($name, '1:*', 'SIZE', false); + +        if (is_array($result)) +            $result = array_sum($result); + +        return $result; +    } + + +    /**       * Subscribe to a specific mailbox(es)       *       * @param array $a_mboxes Mailbox name(s) @@ -3043,130 +3136,120 @@ class rcube_imap      /**       * Create a new mailbox on the server and register it in local cache       * -     * @param string  $name      New mailbox name (as utf-7 string) +     * @param string  $name      New mailbox name       * @param boolean $subscribe True if the new mailbox should be subscribed -     * @param string  Name of the created mailbox, false on error +     * @param boolean True on success       */      function create_mailbox($name, $subscribe=false)      { -        $result = false; - -        // reduce mailbox name to 100 chars -        $name = substr($name, 0, 100); +        $result   = false;          $abs_name = $this->mod_mailbox($name); -        $result = $this->conn->createFolder($abs_name); +        $result   = $this->conn->createFolder($abs_name);          // try to subscribe it          if ($result && $subscribe)              $this->subscribe($name); -        return $result ? $name : false; +        return $result;      }      /**       * Set a new name to an existing mailbox       * -     * @param string $mbox_name Mailbox to rename (as utf-7 string) -     * @param string $new_name  New mailbox name (as utf-7 string) -     * @return string Name of the renames mailbox, False on error +     * @param string $mbox_name Mailbox to rename +     * @param string $new_name  New mailbox name +     * +     * @return boolean True on success       */      function rename_mailbox($mbox_name, $new_name)      {          $result = false; -        // encode mailbox name and reduce it to 100 chars -        $name = substr($new_name, 0, 100); -          // make absolute path -        $mailbox = $this->mod_mailbox($mbox_name); -        $abs_name = $this->mod_mailbox($name); - -        // check if mailbox is subscribed -        $a_subscribed = $this->_list_mailboxes(); -        $subscribed = in_array($mailbox, $a_subscribed); +        $mailbox  = $this->mod_mailbox($mbox_name); +        $abs_name = $this->mod_mailbox($new_name); +        $delm     = $this->get_hierarchy_delimiter(); -        // unsubscribe folder -        if ($subscribed) -            $this->conn->unsubscribe($mailbox); +        // get list of subscribed folders +        if ((strpos($mailbox, '%') === false) && (strpos($mailbox, '*') === false)) { +            $a_subscribed = $this->_list_mailboxes('', $mbox_name . $delm . '*'); +            $subscribed   = $this->mailbox_exists($mbox_name, true); +        } +        else { +            $a_subscribed = $this->_list_mailboxes(); +            $subscribed   = in_array($mailbox, $a_subscribed); +        }          if (strlen($abs_name))              $result = $this->conn->renameFolder($mailbox, $abs_name);          if ($result) { -            $delm = $this->get_hierarchy_delimiter(); +            // unsubscribe the old folder, subscribe the new one +            if ($subscribed) { +                $this->conn->unsubscribe($mailbox); +                $this->conn->subscribe($abs_name); +            }              // check if mailbox children are subscribed -            foreach ($a_subscribed as $c_subscribed) +            foreach ($a_subscribed as $c_subscribed) {                  if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) {                      $this->conn->unsubscribe($c_subscribed);                      $this->conn->subscribe(preg_replace('/^'.preg_quote($mailbox, '/').'/',                          $abs_name, $c_subscribed));                  } +            }              // clear cache              $this->clear_message_cache($mailbox.'.msg');              $this->clear_cache('mailboxes');          } -        // try to subscribe it -        if ($result && $subscribed) -            $this->conn->subscribe($abs_name); - -        return $result ? $name : false; +        return $result;      }      /** -     * Remove mailboxes from server +     * Remove mailbox from server +     * +     * @param string $mbox_name Mailbox name       * -     * @param string|array $mbox_name sMailbox name(s) string/array       * @return boolean True on success       */      function delete_mailbox($mbox_name)      { -        $deleted = false; - -        if (is_array($mbox_name)) -            $a_mboxes = $mbox_name; -        else if (is_string($mbox_name) && strlen($mbox_name)) -            $a_mboxes = explode(',', $mbox_name); +        $result  = false; +        $mailbox = $this->mod_mailbox($mbox_name); +        $delm    = $this->get_hierarchy_delimiter(); -        if (is_array($a_mboxes)) { -            $delimiter = $this->get_hierarchy_delimiter(); -         -            foreach ($a_mboxes as $mbox_name) { -                $mailbox = $this->mod_mailbox($mbox_name); -                $sub_mboxes = $this->conn->listMailboxes('', $mbox_name . $delimiter . '*'); +        // get list of folders +        if ((strpos($mailbox, '%') === false) && (strpos($mailbox, '*') === false)) +            $sub_mboxes = $this->list_unsubscribed('', $mailbox . $delm . '*'); +        else +            $sub_mboxes = $this->list_unsubscribed(); -                // unsubscribe mailbox before deleting -                $this->conn->unsubscribe($mailbox); +        // send delete command to server +        $result = $this->conn->deleteFolder($mailbox); -                // send delete command to server -                $result = $this->conn->deleteFolder($mailbox); -                if ($result) { -                    $deleted = true; -                    $this->clear_message_cache($mailbox.'.msg'); -	            } +        if ($result) { +            // unsubscribe mailbox +            $this->conn->unsubscribe($mailbox); -                foreach ($sub_mboxes as $c_mbox) { -                    if ($c_mbox != 'INBOX') { -                        $this->conn->unsubscribe($c_mbox); -                        $result = $this->conn->deleteFolder($c_mbox); -                        if ($result) { -                            $deleted = true; -    	                    $this->clear_message_cache($c_mbox.'.msg'); -                        } +            foreach ($sub_mboxes as $c_mbox) { +                if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_mbox)) { +                    $this->conn->unsubscribe($c_mbox); +                    if ($this->conn->deleteFolder($c_mbox)) { +	                    $this->clear_message_cache($c_mbox.'.msg');                      }                  }              } -        } -        // clear mailboxlist cache -        if ($deleted) +            // clear mailbox-related cache +            $this->clear_message_cache($mailbox.'.msg');              $this->clear_cache('mailboxes'); +        } -        return $deleted; +        return $result;      } @@ -3208,7 +3291,7 @@ class rcube_imap          }          else {              $a_folders = $this->conn->listMailboxes('', $mbox); -       } +        }          if (is_array($a_folders) && in_array($mbox, $a_folders)) {              $this->icache[$key][] = $mbox; diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php index 16c9d4a64..e5e06c440 100644 --- a/program/include/rcube_imap_generic.php +++ b/program/include/rcube_imap_generic.php @@ -1,6 +1,6 @@  <?php -/* +/**   +-----------------------------------------------------------------------+   | program/include/rcube_imap_generic.php                                |   |                                                                       | @@ -29,45 +29,45 @@  /**   * Struct representing an e-mail message header   * - * @package    Mail - * @author     Aleksander Machniak <alec@alec.pl> + * @package Mail + * @author  Aleksander Machniak <alec@alec.pl>   */  class rcube_mail_header  { -	public $id; -	public $uid; -	public $subject; -	public $from; -	public $to; -	public $cc; -	public $replyto; -	public $in_reply_to; -	public $date; -	public $messageID; -	public $size; -	public $encoding; -	public $charset; -	public $ctype; -	public $flags; -	public $timestamp; -	public $body_structure; -	public $internaldate; -	public $references; -	public $priority; -	public $mdn_to; -	public $mdn_sent = false; -	public $is_draft = false; -	public $seen = false; -	public $deleted = false; -	public $recent = false; -	public $answered = false; -	public $forwarded = false; -	public $junk = false; -	public $flagged = false; -	public $has_children = false; -	public $depth = 0; -	public $unread_children = 0; -	public $others = array(); +    public $id; +    public $uid; +    public $subject; +    public $from; +    public $to; +    public $cc; +    public $replyto; +    public $in_reply_to; +    public $date; +    public $messageID; +    public $size; +    public $encoding; +    public $charset; +    public $ctype; +    public $flags; +    public $timestamp; +    public $body_structure; +    public $internaldate; +    public $references; +    public $priority; +    public $mdn_to; +    public $mdn_sent = false; +    public $is_draft = false; +    public $seen = false; +    public $deleted = false; +    public $recent = false; +    public $answered = false; +    public $forwarded = false; +    public $junk = false; +    public $flagged = false; +    public $has_children = false; +    public $depth = 0; +    public $unread_children = 0; +    public $others = array();  }  // For backward compatibility with cached messages (#1486602) @@ -78,14 +78,15 @@ class iilBasicHeader extends rcube_mail_header  /**   * PHP based wrapper class to connect to an IMAP server   * - * @package    Mail - * @author     Aleksander Machniak <alec@alec.pl> + * @package Mail + * @author  Aleksander Machniak <alec@alec.pl>   */  class rcube_imap_generic  {      public $error;      public $errornum; -	public $message; +    public $result; +    public $resultcode;      public $data = array();      public $flags = array(          'SEEN'     => '\\Seen', @@ -100,11 +101,11 @@ class rcube_imap_generic      );      private $selected; -	private $fp; -	private $host; -	private $logged = false; -	private $capability = array(); -	private $capability_readed = false; +    private $fp; +    private $host; +    private $logged = false; +    private $capability = array(); +    private $capability_readed = false;      private $prefs;      private $cmd_tag;      private $cmd_num = 0; @@ -113,8 +114,9 @@ class rcube_imap_generic      const ERROR_NO = -1;      const ERROR_BAD = -2;      const ERROR_BYE = -3; -    const ERROR_COMMAND = -5;      const ERROR_UNKNOWN = -4; +    const ERROR_COMMAND = -5; +    const ERROR_READONLY = -6;      const COMMAND_NORESPONSE = 1;      const COMMAND_CAPABILITY = 2; @@ -140,16 +142,16 @@ class rcube_imap_generic          if (!$this->fp)              return false; -		if (!empty($this->prefs['debug_mode'])) { -    		write_log('imap', 'C: '. rtrim($string)); -	    } +        if (!empty($this->prefs['debug_mode'])) { +            write_log('imap', 'C: '. rtrim($string)); +        }          $res = fwrite($this->fp, $string . ($endln ? "\r\n" : '')); -   		if ($res === false) { -           	@fclose($this->fp); -           	$this->fp = null; -   		} +        if ($res === false) { +            @fclose($this->fp); +            $this->fp = null; +        }          return $res;      } @@ -168,161 +170,174 @@ class rcube_imap_generic          if (!$this->fp)              return false; -	    if ($endln) -		    $string .= "\r\n"; +        if ($endln) +            $string .= "\r\n"; -	    $res = 0; -	    if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) { -		    for ($i=0, $cnt=count($parts); $i<$cnt; $i++) { -			    if (preg_match('/^\{[0-9]+\}\r\n$/', $parts[$i+1])) { +        $res = 0; +        if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) { +            for ($i=0, $cnt=count($parts); $i<$cnt; $i++) { +                if (preg_match('/^\{[0-9]+\}\r\n$/', $parts[$i+1])) {                      // LITERAL+ support                      if ($this->prefs['literal+'])                          $parts[$i+1] = preg_replace('/([0-9]+)/', '\\1+', $parts[$i+1]); -				    $bytes = $this->putLine($parts[$i].$parts[$i+1], false); +                    $bytes = $this->putLine($parts[$i].$parts[$i+1], false);                      if ($bytes === false)                          return false;                      $res += $bytes;                      // don't wait if server supports LITERAL+ capability                      if (!$this->prefs['literal+']) { -				        $line = $this->readLine(1000); -				        // handle error in command -				        if ($line[0] != '+') -					        return false; -				    } +                        $line = $this->readLine(1000); +                        // handle error in command +                        if ($line[0] != '+') +                            return false; +                    }                      $i++; -			    } -			    else { -				    $bytes = $this->putLine($parts[$i], false); +                } +                else { +                    $bytes = $this->putLine($parts[$i], false);                      if ($bytes === false)                          return false;                      $res += $bytes;                  } -		    } -	    } +            } +        } -	    return $res; +        return $res;      }      function readLine($size=1024)      { -		$line = ''; +        $line = ''; -	    if (!$this->fp) { -    		return NULL; -	    } +        if (!$this->fp) { +            return NULL; +        } -	    if (!$size) { -		    $size = 1024; -	    } +        if (!$size) { +            $size = 1024; +        } -	    do { -		    if (feof($this->fp)) { -			    return $line ? $line : NULL; -		    } +        do { +            if (feof($this->fp)) { +                return $line ? $line : NULL; +            } -    		$buffer = fgets($this->fp, $size); +            $buffer = fgets($this->fp, $size); -    		if ($buffer === false) { -            	@fclose($this->fp); -            	$this->fp = null; -        		break; -    		} -		    if (!empty($this->prefs['debug_mode'])) { -			    write_log('imap', 'S: '. rtrim($buffer)); -    		} +            if ($buffer === false) { +                @fclose($this->fp); +                $this->fp = null; +                break; +            } +            if (!empty($this->prefs['debug_mode'])) { +                write_log('imap', 'S: '. rtrim($buffer)); +            }              $line .= $buffer; -	    } while ($buffer[strlen($buffer)-1] != "\n"); +        } while ($buffer[strlen($buffer)-1] != "\n"); -	    return $line; +        return $line;      }      function multLine($line, $escape=false)      { -	    $line = rtrim($line); -	    if (preg_match('/\{[0-9]+\}$/', $line)) { -		    $out = ''; +        $line = rtrim($line); +        if (preg_match('/\{[0-9]+\}$/', $line)) { +            $out = ''; -		    preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a); -		    $bytes = $a[2][0]; -		    while (strlen($out) < $bytes) { -			    $line = $this->readBytes($bytes); -			    if ($line === NULL) -				    break; -			    $out .= $line; -		    } +            preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a); +            $bytes = $a[2][0]; +            while (strlen($out) < $bytes) { +                $line = $this->readBytes($bytes); +                if ($line === NULL) +                    break; +                $out .= $line; +            } -		    $line = $a[1][0] . ($escape ? $this->escape($out) : $out); -	    } +            $line = $a[1][0] . ($escape ? $this->escape($out) : $out); +        }          return $line;      }      function readBytes($bytes)      { -	    $data = ''; -	    $len  = 0; -	    while ($len < $bytes && !feof($this->fp)) -	    { -		    $d = fread($this->fp, $bytes-$len); -		    if (!empty($this->prefs['debug_mode'])) { -			    write_log('imap', 'S: '. $d); +        $data = ''; +        $len  = 0; +        while ($len < $bytes && !feof($this->fp)) +        { +            $d = fread($this->fp, $bytes-$len); +            if (!empty($this->prefs['debug_mode'])) { +                write_log('imap', 'S: '. $d);              }              $data .= $d; -		    $data_len = strlen($data); -		    if ($len == $data_len) { -    		    break; // nothing was read -> exit to avoid apache lockups -    		} -    		$len = $data_len; -	    } +            $data_len = strlen($data); +            if ($len == $data_len) { +                break; // nothing was read -> exit to avoid apache lockups +            } +            $len = $data_len; +        } -	    return $data; +        return $data;      } -    // don't use it in loops, until you exactly know what you're doing      function readReply(&$untagged=null)      { -	    do { -		    $line = trim($this->readLine(1024)); +        do { +            $line = trim($this->readLine(1024));              // store untagged response lines -		    if ($line[0] == '*') +            if ($line[0] == '*')                  $untagged[] = $line; -	    } while ($line[0] == '*'); +        } while ($line[0] == '*');          if ($untagged)              $untagged = join("\n", $untagged); -	    return $line; +        return $line;      }      function parseResult($string, $err_prefix='')      { -	    if (preg_match('/^[a-z0-9*]+ (OK|NO|BAD|BYE)(.*)$/i', trim($string), $matches)) { -		    $res = strtoupper($matches[1]); +        if (preg_match('/^[a-z0-9*]+ (OK|NO|BAD|BYE)(.*)$/i', trim($string), $matches)) { +            $res = strtoupper($matches[1]);              $str = trim($matches[2]); -		    if ($res == 'OK') { -			    return $this->errornum = self::ERROR_OK; -		    } else if ($res == 'NO') { +            if ($res == 'OK') { +                $this->errornum = self::ERROR_OK; +            } else if ($res == 'NO') {                  $this->errornum = self::ERROR_NO; -		    } else if ($res == 'BAD') { -			    $this->errornum = self::ERROR_BAD; -		    } else if ($res == 'BYE') { +            } else if ($res == 'BAD') { +                $this->errornum = self::ERROR_BAD; +            } else if ($res == 'BYE') {                  @fclose($this->fp);                  $this->fp = null; -			    $this->errornum = self::ERROR_BYE; -		    } +                $this->errornum = self::ERROR_BYE; +            } -            if ($str) -                $this->error = $err_prefix ? $err_prefix.$str : $str; +            if ($str) { +                $str = trim($str); +                // get response string and code (RFC5530) +                if (preg_match("/^\[([a-z-]+)\]/i", $str, $m)) { +                    $this->resultcode = strtoupper($m[1]); +                    $str = trim(substr($str, strlen($m[1]) + 2)); +                } +                else { +                    $this->resultcode = null; +                } +                $this->result = $str; + +                if ($this->errornum != self::ERROR_OK) { +                    $this->error = $err_prefix ? $err_prefix.$str : $str; +                } +            } -	        return $this->errornum; -	    } -	    return self::ERROR_UNKNOWN; +            return $this->errornum; +        } +        return self::ERROR_UNKNOWN;      } -    private function setError($code, $msg='') +    function setError($code, $msg='')      {          $this->errornum = $code;          $this->error    = $msg; @@ -331,59 +346,59 @@ class rcube_imap_generic      // check if $string starts with $match (or * BYE/BAD)      function startsWith($string, $match, $error=false, $nonempty=false)      { -	    $len = strlen($match); -	    if ($len == 0) { -		    return false; -	    } +        $len = strlen($match); +        if ($len == 0) { +            return false; +        }          if (!$this->fp) {              return true;          } -	    if (strncmp($string, $match, $len) == 0) { -		    return true; -	    } -	    if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) { +        if (strncmp($string, $match, $len) == 0) { +            return true; +        } +        if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) {              if (strtoupper($m[1]) == 'BYE') {                  @fclose($this->fp);                  $this->fp = null;              } -		    return true; -	    } +            return true; +        }          if ($nonempty && !strlen($string)) {              return true;          } -	    return false; +        return false;      }      function getCapability($name)      { -	    if (in_array($name, $this->capability)) { -		    return true; -	    } -	    else if ($this->capability_readed) { -		    return false; -	    } +        if (in_array($name, $this->capability)) { +            return true; +        } +        else if ($this->capability_readed) { +            return false; +        } -	    // get capabilities (only once) because initial -	    // optional CAPABILITY response may differ +        // get capabilities (only once) because initial +        // optional CAPABILITY response may differ          $result = $this->execute('CAPABILITY');          if ($result[0] == self::ERROR_OK) {              $this->parseCapability($result[1]);          } -	    $this->capability_readed = true; +        $this->capability_readed = true; -	    if (in_array($name, $this->capability)) { -		    return true; -	    } +        if (in_array($name, $this->capability)) { +            return true; +        } -	    return false; +        return false;      }      function clearCapability()      { -	    $this->capability = array(); -	    $this->capability_readed = false; +        $this->capability = array(); +        $this->capability_readed = false;      }      /** @@ -401,18 +416,18 @@ class rcube_imap_generic              if ($type == 'DIGEST-MD5' && !class_exists('Auth_SASL')) {                  $this->setError(self::ERROR_BYE,                      "The Auth_SASL package is required for DIGEST-MD5 authentication"); -			    return self::ERROR_BAD; +                return self::ERROR_BAD;              } -		    $this->putLine($this->nextTag() . " AUTHENTICATE $type"); -		    $line = trim($this->readLine(1024)); +            $this->putLine($this->nextTag() . " AUTHENTICATE $type"); +            $line = trim($this->readReply()); -		    if ($line[0] == '+') { -			    $challenge = substr($line, 2); +            if ($line[0] == '+') { +                $challenge = substr($line, 2);              }              else {                  return $this->parseResult($line); -		    } +            }              if ($type == 'CRAM-MD5') {                  // RFC2195: CRAM-MD5 @@ -455,10 +470,10 @@ class rcube_imap_generic                  // send result                  $this->putLine($reply); -                $line = $this->readLine(1024); +                $line = trim($this->readReply());                  if ($line[0] == '+') { -			        $challenge = substr($line, 2); +                    $challenge = substr($line, 2);                  }                  else {                      return $this->parseResult($line); @@ -475,7 +490,7 @@ class rcube_imap_generic                  $this->putLine('');              } -            $line = $this->readLine(1024); +            $line = $this->readReply();              $result = $this->parseResult($line);          }          else { // PLAIN @@ -496,29 +511,29 @@ class rcube_imap_generic                      self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY);              }              else { -    		    $this->putLine($this->nextTag() . " AUTHENTICATE PLAIN"); -	    	    $line = trim($this->readLine(1024)); +                $this->putLine($this->nextTag() . " AUTHENTICATE PLAIN"); +                $line = trim($this->readReply()); -		        if ($line[0] != '+') { -    			    return $this->parseResult($line); -	    	    } +                if ($line[0] != '+') { +                    return $this->parseResult($line); +                }                  // send result, get reply and process it                  $this->putLine($reply); -                $line = $this->readLine(1024); +                $line = $this->readReply();                  $result = $this->parseResult($line);              }          }          if ($result == self::ERROR_OK) { -    	    // optional CAPABILITY response -	        if ($line && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { -		        $this->parseCapability($matches[1], true); -	        } +            // optional CAPABILITY response +            if ($line && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { +                $this->parseCapability($matches[1], true); +            }              return $this->fp;          }          else { -            $this->setError($result, "Unable to authenticate user ($type): $line"); +            $this->setError($result, "AUTHENTICATE $type: $line");          }          return $result; @@ -538,9 +553,9 @@ class rcube_imap_generic              $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY);          // re-set capabilities list if untagged CAPABILITY response provided -	    if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) { -		    $this->parseCapability($matches[1], true); -	    } +        if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) { +            $this->parseCapability($matches[1], true); +        }          if ($code == self::ERROR_OK) {              return $this->fp; @@ -556,21 +571,21 @@ class rcube_imap_generic       */      function getHierarchyDelimiter()      { -	    if ($this->prefs['delimiter']) { -    		return $this->prefs['delimiter']; -	    } +        if ($this->prefs['delimiter']) { +            return $this->prefs['delimiter']; +        } -	    // try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8) -	    list($code, $response) = $this->execute('LIST', -	        array($this->escape(''), $this->escape(''))); +        // try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8) +        list($code, $response) = $this->execute('LIST', +            array($this->escape(''), $this->escape('')));          if ($code == self::ERROR_OK) {              $args = $this->tokenizeResponse($response, 4);              $delimiter = $args[3]; -	        if (strlen($delimiter) > 0) { -	            return ($this->prefs['delimiter'] = $delimiter); -	        } +            if (strlen($delimiter) > 0) { +                return ($this->prefs['delimiter'] = $delimiter); +            }          }          return NULL; @@ -588,18 +603,18 @@ class rcube_imap_generic          }          if (!$this->getCapability('NAMESPACE')) { -	        return self::ERROR_BAD; -	    } +            return self::ERROR_BAD; +        } -	    list($code, $response) = $this->execute('NAMESPACE'); +        list($code, $response) = $this->execute('NAMESPACE'); -		if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) { -	        $data = $this->tokenizeResponse(substr($response, 11)); -		} +        if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) { +            $data = $this->tokenizeResponse(substr($response, 11)); +        } -	    if (!is_array($data)) { -	        return $code; -	    } +        if (!is_array($data)) { +            return $code; +        }          $this->prefs['namespace'] = array(              'personal' => $data[0], @@ -612,134 +627,132 @@ class rcube_imap_generic      function connect($host, $user, $password, $options=null)      { -	    // set options -	    if (is_array($options)) { +        // set options +        if (is_array($options)) {              $this->prefs = $options;          }          // set auth method          if (!empty($this->prefs['auth_method'])) {              $auth_method = strtoupper($this->prefs['auth_method']); -	    } else { -    		$auth_method = 'CHECK'; +        } else { +            $auth_method = 'CHECK';          } -	    $result = false; +        $result = false; -	    // initialize connection -	    $this->error    = ''; -	    $this->errornum = self::ERROR_OK; -	    $this->selected = ''; -	    $this->user     = $user; -	    $this->host     = $host; +        // initialize connection +        $this->error    = ''; +        $this->errornum = self::ERROR_OK; +        $this->selected = ''; +        $this->user     = $user; +        $this->host     = $host;          $this->logged   = false; -	    // check input -	    if (empty($host)) { -		    $this->setError(self::ERROR_BAD, "Empty host"); -		    return false; -	    } +        // check input +        if (empty($host)) { +            $this->setError(self::ERROR_BAD, "Empty host"); +            return false; +        }          if (empty($user)) { -    		$this->setError(self::ERROR_NO, "Empty user"); -	    	return false; -	    } -	    if (empty($password)) { -	    	$this->setError(self::ERROR_NO, "Empty password"); -		    return false; -	    } +            $this->setError(self::ERROR_NO, "Empty user"); +            return false; +        } +        if (empty($password)) { +            $this->setError(self::ERROR_NO, "Empty password"); +            return false; +        } -	    if (!$this->prefs['port']) { -		    $this->prefs['port'] = 143; -	    } -	    // check for SSL -	    if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') { -		    $host = $this->prefs['ssl_mode'] . '://' . $host; -	    } +        if (!$this->prefs['port']) { +            $this->prefs['port'] = 143; +        } +        // check for SSL +        if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') { +            $host = $this->prefs['ssl_mode'] . '://' . $host; +        }          // Connect          if ($this->prefs['timeout'] > 0) -	        $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); -	    else -	        $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr); +            $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); +        else +            $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr); -	    if (!$this->fp) { -    		$this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr)); -		    return false; -	    } +        if (!$this->fp) { +            $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr)); +            return false; +        }          if ($this->prefs['timeout'] > 0) -	        stream_set_timeout($this->fp, $this->prefs['timeout']); +            stream_set_timeout($this->fp, $this->prefs['timeout']); -	    $line = trim(fgets($this->fp, 8192)); +        $line = trim(fgets($this->fp, 8192)); -	    if ($this->prefs['debug_mode'] && $line) { -		    write_log('imap', 'S: '. $line); +        if ($this->prefs['debug_mode'] && $line) { +            write_log('imap', 'S: '. $line);          } -	    // Connected to wrong port or connection error? -	    if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) { -		    if ($line) -			    $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line); -		    else -			    $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']); - -	        $this->setError(self::ERROR_BAD, $error); -            $this->close(); -	        return false; -	    } +        // Connected to wrong port or connection error? +        if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) { +            if ($line) +                $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line); +            else +                $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']); -	    // RFC3501 [7.1] optional CAPABILITY response -	    if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { -		    $this->parseCapability($matches[1], true); -	    } +            $this->setError(self::ERROR_BAD, $error); +            $this->closeConnection(); +            return false; +        } -	    $this->message = $line; +        // RFC3501 [7.1] optional CAPABILITY response +        if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { +            $this->parseCapability($matches[1], true); +        } -	    // TLS connection -	    if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { -        	if (version_compare(PHP_VERSION, '5.1.0', '>=')) { -               	$res = $this->execute('STARTTLS'); +        // TLS connection +        if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { +            if (version_compare(PHP_VERSION, '5.1.0', '>=')) { +                $res = $this->execute('STARTTLS');                  if ($res[0] != self::ERROR_OK) { -                    $this->close(); +                    $this->closeConnection();                      return false;                  } -			    if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { -				    $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); -                    $this->close(); -				    return false; -			    } +                if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { +                    $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); +                    $this->closeConnection(); +                    return false; +                } -			    // Now we're secure, capabilities need to be reread -			    $this->clearCapability(); -        	} -	    } +                // Now we're secure, capabilities need to be reread +                $this->clearCapability(); +            } +        } -	    $auth_methods = array(); +        $auth_methods = array();          $result       = null; -	    // check for supported auth methods -	    if ($auth_method == 'CHECK') { -		    if ($this->getCapability('AUTH=DIGEST-MD5')) { -			    $auth_methods[] = 'DIGEST-MD5'; -		    } -		    if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) { -			    $auth_methods[] = 'CRAM-MD5'; -		    } -		    if ($this->getCapability('AUTH=PLAIN')) { -			    $auth_methods[] = 'PLAIN'; -		    } +        // check for supported auth methods +        if ($auth_method == 'CHECK') { +            if ($this->getCapability('AUTH=DIGEST-MD5')) { +                $auth_methods[] = 'DIGEST-MD5'; +            } +            if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) { +                $auth_methods[] = 'CRAM-MD5'; +            } +            if ($this->getCapability('AUTH=PLAIN')) { +                $auth_methods[] = 'PLAIN'; +            }              // RFC 2595 (LOGINDISABLED) LOGIN disabled when connection is not secure -		    if (!$this->getCapability('LOGINDISABLED')) { -			    $auth_methods[] = 'LOGIN'; -		    } -	    } +            if (!$this->getCapability('LOGINDISABLED')) { +                $auth_methods[] = 'LOGIN'; +            } +        }          else {              // Prevent from sending credentials in plain text when connection is not secure -		    if ($auth_method == 'LOGIN' && $this->getCapability('LOGINDISABLED')) { -			    $this->setError(self::ERROR_BAD, "Login disabled by IMAP server"); -                $this->close(); -			    return false; +            if ($auth_method == 'LOGIN' && $this->getCapability('LOGINDISABLED')) { +                $this->setError(self::ERROR_BAD, "Login disabled by IMAP server"); +                $this->closeConnection(); +                return false;              }              // replace AUTH with CRAM-MD5 for backward compat.              $auth_methods[] = $auth_method == 'AUTH' ? 'CRAM-MD5' : $auth_method; @@ -753,61 +766,68 @@ class rcube_imap_generic              switch ($method) {              case 'DIGEST-MD5':              case 'CRAM-MD5': -	        case 'PLAIN': -			    $result = $this->authenticate($user, $password, $method); -		        break; +            case 'PLAIN': +                $result = $this->authenticate($user, $password, $method); +                break;              case 'LOGIN': -       	        $result = $this->login($user, $password); +                $result = $this->login($user, $password);                  break;              default:                  $this->setError(self::ERROR_BAD, "Configuration error. Unknown auth method: $method");              } -		    if (is_resource($result)) { -			    break; -		    } -	    } +            if (is_resource($result)) { +                break; +            } +        }          // Connected and authenticated -	    if (is_resource($result)) { +        if (is_resource($result)) {              if ($this->prefs['force_caps']) { -			    $this->clearCapability(); +                $this->clearCapability();              }              $this->logged = true; -		    return true; +            return true;          } -        // Close connection -        $this->close(); +        $this->closeConnection();          return false;      }      function connected()      { -		return ($this->fp && $this->logged) ? true : false; +        return ($this->fp && $this->logged) ? true : false;      } -    function close() +    function closeConnection()      { -	    if ($this->putLine($this->nextTag() . ' LOGOUT')) { -    	    $this->readReply(); +        if ($this->putLine($this->nextTag() . ' LOGOUT')) { +            $this->readReply();          } -		@fclose($this->fp); -		$this->fp = false; +        @fclose($this->fp); +        $this->fp = false;      } +    /** +     * Executes SELECT command (if mailbox is already not in selected state) +     * +     * @param string $mailbox Mailbox name +     * +     * @return boolean True on success, false on error +     * @access public +     */      function select($mailbox)      { -	    if (!strlen($mailbox)) { -		    return false; -	    } +        if (!strlen($mailbox)) { +            return false; +        } -	    if ($this->selected == $mailbox) { -		    return true; -	    } +        if ($this->selected == $mailbox) { +            return true; +        }  /*      Temporary commented out because Courier returns \Noselect for INBOX      Requires more investigation @@ -823,26 +843,28 @@ class rcube_imap_generic          if ($code == self::ERROR_OK) {              $response = explode("\r\n", $response);              foreach ($response as $line) { -    			if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT)$/i', $line, $m)) { -	    		    $this->data[strtoupper($m[2])] = (int) $m[1]; -		    	} -			    else if (preg_match('/^\* OK \[(UIDNEXT|UIDVALIDITY|UNSEEN) ([0-9]+)\]/i', $line, $match)) { -			        $this->data[strtoupper($match[1])] = (int) $match[2]; -			    } -			    else if (preg_match('/^\* OK \[PERMANENTFLAGS \(([^\)]+)\)\]/iU', $line, $match)) { -			        $this->data['PERMANENTFLAGS'] = explode(' ', $match[1]); -			    } +                if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT)$/i', $line, $m)) { +                    $this->data[strtoupper($m[2])] = (int) $m[1]; +                } +                else if (preg_match('/^\* OK \[(UIDNEXT|UIDVALIDITY|UNSEEN) ([0-9]+)\]/i', $line, $match)) { +                    $this->data[strtoupper($match[1])] = (int) $match[2]; +                } +                else if (preg_match('/^\* OK \[PERMANENTFLAGS \(([^\)]+)\)\]/iU', $line, $match)) { +                    $this->data['PERMANENTFLAGS'] = explode(' ', $match[1]); +                }              } -		    $this->selected = $mailbox; -			return true; -		} +            $this->data['READ-WRITE'] = $this->resultcode != 'READ-ONLY'; + +            $this->selected = $mailbox; +            return true; +        }          return false;      }      /** -     * Executes STATUS comand +     * Executes STATUS command       *       * @param string $mailbox Mailbox name       * @param array  $items   Additional requested item names. By default @@ -855,9 +877,9 @@ class rcube_imap_generic       */      function status($mailbox, $items=array())      { -	    if (!strlen($mailbox)) { -		    return false; -	    } +        if (!strlen($mailbox)) { +            return false; +        }          if (!in_array('MESSAGES', $items)) {              $items[] = 'MESSAGES'; @@ -881,36 +903,157 @@ class rcube_imap_generic              $this->data['STATUS:'.$mailbox] = $result; -			return $result; -		} +            return $result; +        } + +        return false; +    } + +    /** +     * Executes EXPUNGE command +     * +     * @param string $mailbox  Mailbox name +     * @param string $messages Message UIDs to expunge +     * +     * @return boolean True on success, False on error +     * @access public +     */ +    function expunge($mailbox, $messages=NULL) +    { +        if (!$this->select($mailbox)) { +            return false; +        } + +        if (!$this->data['READ-WRITE']) { +            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'EXPUNGE'); +            return false; +        } + +        // Clear internal status cache +        unset($this->data['STATUS:'.$mailbox]); + +        if ($messages) +            $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE); +        else +            $result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE); + +        if ($result == self::ERROR_OK) { +            $this->selected = ''; // state has changed, need to reselect +            return true; +        }          return false;      } -    function checkForRecent($mailbox) +    /** +     * Executes CLOSE command +     * +     * @return boolean True on success, False on error +     * @access public +     * @since 0.5 +     */ +    function close()      { -	    if (!strlen($mailbox)) { -		    $mailbox = 'INBOX'; -	    } +        $result = $this->execute('CLOSE', NULL, self::COMMAND_NORESPONSE); -	    $this->select($mailbox); +        if ($result == self::ERROR_OK) { +            $this->selected = ''; +            return true; +        } -	    if ($this->selected == $mailbox) { -		    return $this->data['RECENT']; -	    } +        return false; +    } -	    return false; +    /** +     * Executes SUBSCRIBE command +     * +     * @param string $mailbox Mailbox name +     * +     * @return boolean True on success, False on error +     * @access public +     */ +    function subscribe($mailbox) +    { +        $result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)), +            self::COMMAND_NORESPONSE); + +        return ($result == self::ERROR_OK); +    } + +    /** +     * Executes UNSUBSCRIBE command +     * +     * @param string $mailbox Mailbox name +     * +     * @return boolean True on success, False on error +     * @access public +     */ +    function unsubscribe($mailbox) +    { +        $result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)), +            self::COMMAND_NORESPONSE); + +        return ($result == self::ERROR_OK);      } +    /** +     * Executes DELETE command +     * +     * @param string $mailbox Mailbox name +     * +     * @return boolean True on success, False on error +     * @access public +     */ +    function deleteFolder($mailbox) +    { +        $result = $this->execute('DELETE', array($this->escape($mailbox)), +            self::COMMAND_NORESPONSE); + +        return ($result == self::ERROR_OK); +    } + +    /** +     * Removes all messages in a folder +     * +     * @param string $mailbox Mailbox name +     * +     * @return boolean True on success, False on error +     * @access public +     */ +    function clearFolder($mailbox) +    { +        $num_in_trash = $this->countMessages($mailbox); +        if ($num_in_trash > 0) { +            $res = $this->delete($mailbox, '1:*'); +        } + +        if ($res) { +            if ($this->selected == $mailbox) +                $res = $this->close(); +            else +                $res = $this->expunge($mailbox); +        } + +        return $res; +    } + +    /** +     * Returns count of all messages in a folder +     * +     * @param string $mailbox Mailbox name +     * +     * @return int Number of messages, False on error +     * @access public +     */      function countMessages($mailbox, $refresh = false)      { -	    if ($refresh) { -		    $this->selected = ''; -	    } +        if ($refresh) { +            $this->selected = ''; +        } -	    if ($this->selected == $mailbox) { -		    return $this->data['EXISTS']; -	    } +        if ($this->selected == $mailbox) { +            return $this->data['EXISTS']; +        }          // Check internal cache          $cache = $this->data['STATUS:'.$mailbox]; @@ -928,6 +1071,29 @@ class rcube_imap_generic      }      /** +     * Returns count of messages with \Recent flag in a folder +     * +     * @param string $mailbox Mailbox name +     * +     * @return int Number of messages, False on error +     * @access public +     */ +    function countRecent($mailbox) +    { +        if (!strlen($mailbox)) { +            $mailbox = 'INBOX'; +        } + +        $this->select($mailbox); + +        if ($this->selected == $mailbox) { +            return $this->data['RECENT']; +        } + +        return false; +    } + +    /**       * Returns count of messages without \Seen flag in a specified folder       *       * @param string $mailbox Mailbox name @@ -960,218 +1126,218 @@ class rcube_imap_generic      function sort($mailbox, $field, $add='', $is_uid=FALSE, $encoding = 'US-ASCII')      { -	    $field = strtoupper($field); -	    if ($field == 'INTERNALDATE') { -	        $field = 'ARRIVAL'; -	    } +        $field = strtoupper($field); +        if ($field == 'INTERNALDATE') { +            $field = 'ARRIVAL'; +        } -	    $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, +        $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,              'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); -	    if (!$fields[$field]) { -	        return false; -	    } +        if (!$fields[$field]) { +            return false; +        } -	    if (!$this->select($mailbox)) { -	        return false; -	    } +        if (!$this->select($mailbox)) { +            return false; +        } -	    // message IDs -	    if (!empty($add)) -		    $add = $this->compressMessageSet($add); +        // message IDs +        if (!empty($add)) +            $add = $this->compressMessageSet($add); -	    list($code, $response) = $this->execute($is_uid ? 'UID SORT' : 'SORT', -	        array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : ''))); +        list($code, $response) = $this->execute($is_uid ? 'UID SORT' : 'SORT', +            array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : ''))); -	    if ($code == self::ERROR_OK) { -	        // remove prefix and \r\n from raw response +        if ($code == self::ERROR_OK) { +            // remove prefix and \r\n from raw response              $response = str_replace("\r\n", '', substr($response, 7)); -	        return preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY); -	    } +            return preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY); +        }          return false;      }      function fetchHeaderIndex($mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=false)      { -	    if (is_array($message_set)) { -		    if (!($message_set = $this->compressMessageSet($message_set))) -			    return false; -	    } else { -		    list($from_idx, $to_idx) = explode(':', $message_set); -		    if (empty($message_set) || -			    (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { -			    return false; -		    } -	    } +        if (is_array($message_set)) { +            if (!($message_set = $this->compressMessageSet($message_set))) +                return false; +        } else { +            list($from_idx, $to_idx) = explode(':', $message_set); +            if (empty($message_set) || +                (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { +                return false; +            } +        } -	    $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); +        $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); -    	$fields_a['DATE']         = 1; -	    $fields_a['INTERNALDATE'] = 4; -    	$fields_a['ARRIVAL'] 	  = 4; -	    $fields_a['FROM']         = 1; -    	$fields_a['REPLY-TO']     = 1; -	    $fields_a['SENDER']       = 1; -    	$fields_a['TO']           = 1; -	    $fields_a['CC']           = 1; -    	$fields_a['SUBJECT']      = 1; -	    $fields_a['UID']          = 2; -    	$fields_a['SIZE']         = 2; -	    $fields_a['SEEN']         = 3; -    	$fields_a['RECENT']       = 3; -	    $fields_a['DELETED']      = 3; +        $fields_a['DATE']         = 1; +        $fields_a['INTERNALDATE'] = 4; +        $fields_a['ARRIVAL']      = 4; +        $fields_a['FROM']         = 1; +        $fields_a['REPLY-TO']     = 1; +        $fields_a['SENDER']       = 1; +        $fields_a['TO']           = 1; +        $fields_a['CC']           = 1; +        $fields_a['SUBJECT']      = 1; +        $fields_a['UID']          = 2; +        $fields_a['SIZE']         = 2; +        $fields_a['SEEN']         = 3; +        $fields_a['RECENT']       = 3; +        $fields_a['DELETED']      = 3; -    	if (!($mode = $fields_a[$index_field])) { -	    	return false; -	    } +        if (!($mode = $fields_a[$index_field])) { +            return false; +        } -    	/*  Do "SELECT" command */ -	    if (!$this->select($mailbox)) { -		    return false; -	    } +        /*  Do "SELECT" command */ +        if (!$this->select($mailbox)) { +            return false; +        } -    	// build FETCH command string -	    $key     = $this->nextTag(); -	    $cmd     = $uidfetch ? 'UID FETCH' : 'FETCH'; -	    $deleted = $skip_deleted ? ' FLAGS' : ''; +        // build FETCH command string +        $key     = $this->nextTag(); +        $cmd     = $uidfetch ? 'UID FETCH' : 'FETCH'; +        $deleted = $skip_deleted ? ' FLAGS' : ''; -	    if ($mode == 1 && $index_field == 'DATE') -		    $request = " $cmd $message_set (INTERNALDATE BODY.PEEK[HEADER.FIELDS (DATE)]$deleted)"; -	    else if ($mode == 1) -		    $request = " $cmd $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)]$deleted)"; -	    else if ($mode == 2) { -		    if ($index_field == 'SIZE') -			    $request = " $cmd $message_set (RFC822.SIZE$deleted)"; -		    else -			    $request = " $cmd $message_set ($index_field$deleted)"; -	    } else if ($mode == 3) -		    $request = " $cmd $message_set (FLAGS)"; -	    else // 4 -		    $request = " $cmd $message_set (INTERNALDATE$deleted)"; +        if ($mode == 1 && $index_field == 'DATE') +            $request = " $cmd $message_set (INTERNALDATE BODY.PEEK[HEADER.FIELDS (DATE)]$deleted)"; +        else if ($mode == 1) +            $request = " $cmd $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)]$deleted)"; +        else if ($mode == 2) { +            if ($index_field == 'SIZE') +                $request = " $cmd $message_set (RFC822.SIZE$deleted)"; +            else +                $request = " $cmd $message_set ($index_field$deleted)"; +        } else if ($mode == 3) +            $request = " $cmd $message_set (FLAGS)"; +        else // 4 +            $request = " $cmd $message_set (INTERNALDATE$deleted)"; -	    $request = $key . $request; +        $request = $key . $request; -	    if (!$this->putLine($request)) { +        if (!$this->putLine($request)) {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); -		    return false; +            return false;          } -	    $result = array(); +        $result = array(); -	    do { -		    $line = rtrim($this->readLine(200)); -		    $line = $this->multLine($line); +        do { +            $line = rtrim($this->readLine(200)); +            $line = $this->multLine($line); -		    if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { -            	$id     = $m[1]; -			    $flags  = NULL; +            if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { +                $id     = $m[1]; +                $flags  = NULL; -			    if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { -				    $flags = explode(' ', strtoupper($matches[1])); -				    if (in_array('\\DELETED', $flags)) { -					    $deleted[$id] = $id; -					    continue; -				    } -			    } +                if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { +                    $flags = explode(' ', strtoupper($matches[1])); +                    if (in_array('\\DELETED', $flags)) { +                        $deleted[$id] = $id; +                        continue; +                    } +                } -			    if ($mode == 1 && $index_field == 'DATE') { -				    if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) { -					    $value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]); -					    $value = trim($value); -					    $result[$id] = $this->strToTime($value); -				    } -				    // non-existent/empty Date: header, use INTERNALDATE -				    if (empty($result[$id])) { -					    if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) -						    $result[$id] = $this->strToTime($matches[1]); -					    else -						    $result[$id] = 0; -				    } -			    } else if ($mode == 1) { -				    if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) { -					    $value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]); -					    $result[$id] = trim($value); -				    } else { -					    $result[$id] = ''; -				    } -			    } else if ($mode == 2) { -				    if (preg_match('/\((UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { -					    $result[$id] = trim($matches[2]); -				    } else { -					    $result[$id] = 0; -				    } -			    } else if ($mode == 3) { -				    if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { -					    $flags = explode(' ', $matches[1]); -				    } -				    $result[$id] = in_array('\\'.$index_field, $flags) ? 1 : 0; -			    } else if ($mode == 4) { -				    if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) { -					    $result[$id] = $this->strToTime($matches[1]); -				    } else { -					    $result[$id] = 0; -				    } -			    } -		    } -	    } while (!$this->startsWith($line, $key, true, true)); +                if ($mode == 1 && $index_field == 'DATE') { +                    if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) { +                        $value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]); +                        $value = trim($value); +                        $result[$id] = $this->strToTime($value); +                    } +                    // non-existent/empty Date: header, use INTERNALDATE +                    if (empty($result[$id])) { +                        if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) +                            $result[$id] = $this->strToTime($matches[1]); +                        else +                            $result[$id] = 0; +                    } +                } else if ($mode == 1) { +                    if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) { +                        $value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]); +                        $result[$id] = trim($value); +                    } else { +                        $result[$id] = ''; +                    } +                } else if ($mode == 2) { +                    if (preg_match('/\((UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { +                        $result[$id] = trim($matches[2]); +                    } else { +                        $result[$id] = 0; +                    } +                } else if ($mode == 3) { +                    if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { +                        $flags = explode(' ', $matches[1]); +                    } +                    $result[$id] = in_array('\\'.$index_field, $flags) ? 1 : 0; +                } else if ($mode == 4) { +                    if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) { +                        $result[$id] = $this->strToTime($matches[1]); +                    } else { +                        $result[$id] = 0; +                    } +                } +            } +        } while (!$this->startsWith($line, $key, true, true)); -	    return $result; +        return $result;      }      static function compressMessageSet($messages, $force=false)      { -	    // given a comma delimited list of independent mid's, -	    // compresses by grouping sequences together +        // given a comma delimited list of independent mid's, +        // compresses by grouping sequences together          if (!is_array($messages)) { -	        // if less than 255 bytes long, let's not bother -	        if (!$force && strlen($messages)<255) { -	            return $messages; -	        } +            // if less than 255 bytes long, let's not bother +            if (!$force && strlen($messages)<255) { +                return $messages; +           } -    	    // see if it's already been compressed -	        if (strpos($messages, ':') !== false) { -	            return $messages; -	        } +            // see if it's already been compressed +            if (strpos($messages, ':') !== false) { +                return $messages; +            } -	        // separate, then sort -	        $messages = explode(',', $messages); +            // separate, then sort +            $messages = explode(',', $messages);          } -	    sort($messages); +        sort($messages); -	    $result = array(); -	    $start  = $prev = $messages[0]; +        $result = array(); +        $start  = $prev = $messages[0]; -	    foreach ($messages as $id) { -		    $incr = $id - $prev; -		    if ($incr > 1) {			//found a gap -			    if ($start == $prev) { -			        $result[] = $prev;	//push single id -			    } else { -			        $result[] = $start . ':' . $prev;   //push sequence as start_id:end_id -			    } -        		$start = $id;			//start of new sequence -		    } -		    $prev = $id; -	    } +        foreach ($messages as $id) { +            $incr = $id - $prev; +            if ($incr > 1) { // found a gap +                if ($start == $prev) { +                    $result[] = $prev; // push single id +                } else { +                    $result[] = $start . ':' . $prev; // push sequence as start_id:end_id +                } +                $start = $id; // start of new sequence +            } +            $prev = $id; +        } -	    // handle the last sequence/id -	    if ($start == $prev) { -	        $result[] = $prev; -	    } else { -    	    $result[] = $start.':'.$prev; -	    } +        // handle the last sequence/id +        if ($start == $prev) { +            $result[] = $prev; +        } else { +            $result[] = $start.':'.$prev; +        } -	    // return as comma separated string -	    return implode(',', $result); +        // return as comma separated string +        return implode(',', $result);      }      static function uncompressMessageSet($messages)      { -	    $result   = array(); -	    $messages = explode(',', $messages); +        $result   = array(); +        $messages = explode(',', $messages);          foreach ($messages as $part) {              $items = explode(':', $part); @@ -1196,13 +1362,13 @@ class rcube_imap_generic       */      function UID2ID($mailbox, $uid)      { -	    if ($uid > 0) { -    		$id_a = $this->search($mailbox, "UID $uid"); -	    	if (is_array($id_a) && count($id_a) == 1) { -		    	return (int) $id_a[0]; -		    } -	    } -	    return null; +        if ($uid > 0) { +            $id_a = $this->search($mailbox, "UID $uid"); +            if (is_array($id_a) && count($id_a) == 1) { +                return (int) $id_a[0]; +            } +        } +        return null;      }      /** @@ -1216,417 +1382,408 @@ class rcube_imap_generic       */      function ID2UID($mailbox, $id)      { -	    if (empty($id) || $id < 0) { -	        return 	null; -	    } +        if (empty($id) || $id < 0) { +            return 	null; +        } -    	if (!$this->select($mailbox)) { +        if (!$this->select($mailbox)) {              return null;          }          list($code, $response) = $this->execute('FETCH', array($id, '(UID)'));          if ($code == self::ERROR_OK && preg_match("/^\* $id FETCH \(UID (.*)\)/i", $response, $m)) { -			return (int) $m[1]; +            return (int) $m[1];          } -    	return null; +        return null;      }      function fetchUIDs($mailbox, $message_set=null)      { -	    if (is_array($message_set)) -		    $message_set = join(',', $message_set); +        if (is_array($message_set)) +            $message_set = join(',', $message_set);          else if (empty($message_set)) -		    $message_set = '1:*'; +            $message_set = '1:*'; -	    return $this->fetchHeaderIndex($mailbox, $message_set, 'UID', false); +        return $this->fetchHeaderIndex($mailbox, $message_set, 'UID', false);      }      function fetchHeaders($mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='')      { -	    $result = array(); +        $result = array(); -	    if (!$this->select($mailbox)) { -		    return false; -	    } +        if (!$this->select($mailbox)) { +            return false; +        } -	    $message_set = $this->compressMessageSet($message_set); +        $message_set = $this->compressMessageSet($message_set); -	    if ($add) -		    $add = ' '.trim($add); +        if ($add) +            $add = ' '.trim($add); -	    /* FETCH uid, size, flags and headers */ -	    $key  	  = $this->nextTag(); -	    $request  = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set "; -	    $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE "; -	    if ($bodystr) -		    $request .= "BODYSTRUCTURE "; -	    $request .= "BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT CONTENT-TYPE "; -	    $request .= "LIST-POST DISPOSITION-NOTIFICATION-TO".$add.")])"; +        /* FETCH uid, size, flags and headers */ +        $key      = $this->nextTag(); +        $request  = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set "; +        $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE "; +        if ($bodystr) +            $request .= "BODYSTRUCTURE "; +        $request .= "BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT CONTENT-TYPE "; +        $request .= "LIST-POST DISPOSITION-NOTIFICATION-TO".$add.")])"; -	    if (!$this->putLine($request)) { +        if (!$this->putLine($request)) {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); -		    return false; -	    } -	    do { -		    $line = $this->readLine(4096); -		    $line = $this->multLine($line); +            return false; +        } +        do { +            $line = $this->readLine(4096); +            $line = $this->multLine($line);              if (!$line)                  break; -		    if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { -			    $id = intval($m[1]); +            if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { +                $id = intval($m[1]); -			    $result[$id]            = new rcube_mail_header; -			    $result[$id]->id        = $id; -			    $result[$id]->subject   = ''; -			    $result[$id]->messageID = 'mid:' . $id; +                $result[$id]            = new rcube_mail_header; +                $result[$id]->id        = $id; +                $result[$id]->subject   = ''; +                $result[$id]->messageID = 'mid:' . $id; -			    $lines = array(); -			    $ln = 0; +                $lines = array(); +                $ln = 0; -			    // Sample reply line: -			    // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) -			    // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) -			    // BODY[HEADER.FIELDS ... +                // Sample reply line: +                // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) +                // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) +                // BODY[HEADER.FIELDS ... -			    if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) { -				    $str = $matches[1]; +                if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) { +                    $str = $matches[1]; -				    // swap parents with quotes, then explode -				    $str = preg_replace('/[()]/', '"', $str); -				    $a = rcube_explode_quoted_string(' ', $str); +                    // swap parents with quotes, then explode +                    $str = preg_replace('/[()]/', '"', $str); +                    $a = rcube_explode_quoted_string(' ', $str); -				    // did we get the right number of replies? -				    $parts_count = count($a); -				    if ($parts_count>=6) { -					    for ($i=0; $i<$parts_count; $i=$i+2) { -						    if ($a[$i] == 'UID') -							    $result[$id]->uid = intval($a[$i+1]); -						    else if ($a[$i] == 'RFC822.SIZE') -							    $result[$id]->size = intval($a[$i+1]); -    						else if ($a[$i] == 'INTERNALDATE') -	    						$time_str = $a[$i+1]; -		    				else if ($a[$i] == 'FLAGS') -			    				$flags_str = $a[$i+1]; -				    	} +                    // did we get the right number of replies? +                    $parts_count = count($a); +                    if ($parts_count>=6) { +                        for ($i=0; $i<$parts_count; $i=$i+2) { +                            if ($a[$i] == 'UID') { +                                $result[$id]->uid = intval($a[$i+1]); +                            } +                            else if ($a[$i] == 'RFC822.SIZE') { +                                $result[$id]->size = intval($a[$i+1]); +                            } +                            else if ($a[$i] == 'INTERNALDATE') { +                                $time_str = $a[$i+1]; +                            } +                            else if ($a[$i] == 'FLAGS') { +                                $flags_str = $a[$i+1]; +                            } +                        } -					    $time_str = str_replace('"', '', $time_str); +                        $time_str = str_replace('"', '', $time_str); -					    // if time is gmt... -		                $time_str = str_replace('GMT','+0000',$time_str); +                        // if time is gmt... +                        $time_str = str_replace('GMT','+0000',$time_str); -					    $result[$id]->internaldate = $time_str; -    					$result[$id]->timestamp = $this->StrToTime($time_str); -	    				$result[$id]->date = $time_str; -		    		} +                        $result[$id]->internaldate = $time_str; +                        $result[$id]->timestamp    = $this->StrToTime($time_str); +                        $result[$id]->date         = $time_str; +                    } -			    	// BODYSTRUCTURE -				    if($bodystr) { -					    while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) { -						    $line2 = $this->readLine(1024); -    						$line .= $this->multLine($line2, true); -	    				} -		    			$result[$id]->body_structure = $m[1]; -			    	} +                    // BODYSTRUCTURE +                    if ($bodystr) { +                        while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) { +                            $line2 = $this->readLine(1024); +                            $line .= $this->multLine($line2, true); +                        } +                        $result[$id]->body_structure = $m[1]; +                    } -    				// the rest of the result -	    			preg_match('/ BODY\[HEADER.FIELDS \(.*?\)\]\s*(.*)$/s', $line, $m); -		    		$reslines = explode("\n", trim($m[1], '"')); -			    	// re-parse (see below) -				    foreach ($reslines as $resln) { -		    			if (ord($resln[0])<=32) { -			    			$lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($resln); -				    	} else { -					    	$lines[++$ln] = trim($resln); -					    } -    				} -	    		} +                    // the rest of the result +                    if (preg_match('/ BODY\[HEADER.FIELDS \(.*?\)\]\s*(.*)$/s', $line, $m)) { +                        $reslines = explode("\n", trim($m[1], '"')); +                        // re-parse (see below) +                        foreach ($reslines as $resln) { +                            if (ord($resln[0])<=32) { +                                $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($resln); +                            } else { +                                $lines[++$ln] = trim($resln); +                            } +                        } +                    } +                } -				// Start parsing headers.  The problem is, some header "lines" take up multiple lines. -				// So, we'll read ahead, and if the one we're reading now is a valid header, we'll -				// process the previous line.  Otherwise, we'll keep adding the strings until we come -				// to the next valid header line. +                // Start parsing headers.  The problem is, some header "lines" take up multiple lines. +                // So, we'll read ahead, and if the one we're reading now is a valid header, we'll +                // process the previous line.  Otherwise, we'll keep adding the strings until we come +                // to the next valid header line. -			    do { -				    $line = rtrim($this->readLine(300), "\r\n"); +                do { +                    $line = rtrim($this->readLine(300), "\r\n"); -    				// The preg_match below works around communigate imap, which outputs " UID <number>)". -	    			// Without this, the while statement continues on and gets the "FH0 OK completed" message. -		    		// If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. -			    	// This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing -				    // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin -    				// An alternative might be: -	    			// if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break; -		    		// however, unsure how well this would work with all imap clients. -			    	if (preg_match("/^\s*UID [0-9]+\)$/", $line)) { -				        break; -				    } +                    // The preg_match below works around communigate imap, which outputs " UID <number>)". +                    // Without this, the while statement continues on and gets the "FH0 OK completed" message. +                    // If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. +                    // This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing +                    // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin +                    // An alternative might be: +                    // if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break; +                    // however, unsure how well this would work with all imap clients. +                    if (preg_match("/^\s*UID [0-9]+\)$/", $line)) { +                        break; +                    } -    				// handle FLAGS reply after headers (AOL, Zimbra?) -	    			if (preg_match('/\s+FLAGS \((.*)\)\)$/', $line, $matches)) { -		    			$flags_str = $matches[1]; -			    		break; -				    } +                    // handle FLAGS reply after headers (AOL, Zimbra?) +                    if (preg_match('/\s+FLAGS \((.*)\)\)$/', $line, $matches)) { +                        $flags_str = $matches[1]; +                        break; +                    } -    				if (ord($line[0])<=32) { -	    				$lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line); -		    		} else { -			    		$lines[++$ln] = trim($line); -				    } -    			// patch from "Maksim Rubis" <siburny@hotmail.com> -	    		} while ($line[0] != ')' && !$this->startsWith($line, $key, true)); +                    if (ord($line[0])<=32) { +                        $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line); +                    } else { +                        $lines[++$ln] = trim($line); +                    } +                // patch from "Maksim Rubis" <siburny@hotmail.com> +                } while ($line[0] != ')' && !$this->startsWith($line, $key, true)); -    			if (strncmp($line, $key, strlen($key))) { -	    			// process header, fill rcube_mail_header obj. -		    		// initialize -			    	if (is_array($headers)) { -				    	reset($headers); -					    while (list($k, $bar) = each($headers)) { -    						$headers[$k] = ''; -	    				} -		    		} +                if (strncmp($line, $key, strlen($key))) { +                    // process header, fill rcube_mail_header obj. +                    // initialize +                    if (is_array($headers)) { +                        reset($headers); +                        while (list($k, $bar) = each($headers)) { +                            $headers[$k] = ''; +                        } +                    } -			    	// create array with header field:data -    				while ( list($lines_key, $str) = each($lines) ) { -	    				list($field, $string) = $this->splitHeaderLine($str); +                    // create array with header field:data +                    while (list($lines_key, $str) = each($lines)) { +                        list($field, $string) = $this->splitHeaderLine($str); -			    		$field  = strtolower($field); -				    	$string = preg_replace('/\n\s*/', ' ', $string); +                        $field  = strtolower($field); +                        $string = preg_replace('/\n\s*/', ' ', $string); -    					switch ($field) { -	        			case 'date'; -			    			$result[$id]->date = $string; -				    		$result[$id]->timestamp = $this->strToTime($string); -		    		    	break; -    					case 'from': -	    					$result[$id]->from = $string; -		    				break; -			    		case 'to': -				    		$result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string); -					    	break; -    					case 'subject': -	    					$result[$id]->subject = $string; -		    				break; -			    		case 'reply-to': -				    		$result[$id]->replyto = $string; -					    	break; -    					case 'cc': -	    					$result[$id]->cc = $string; -		    				break; -    					case 'bcc': -	    					$result[$id]->bcc = $string; -		    				break; -			    		case 'content-transfer-encoding': -				    		$result[$id]->encoding = $string; -					    	break; -    					case 'content-type': -	    					$ctype_parts = preg_split('/[; ]/', $string); -		    				$result[$id]->ctype = array_shift($ctype_parts); -			    			if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) { -				    			$result[$id]->charset = $regs[1]; -					    	} -        					break; -					    case 'in-reply-to': -			    			$result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string); -				    		break; -    					case 'references': -	    					$result[$id]->references = $string; -		    				break; -			    		case 'return-receipt-to': -				    	case 'disposition-notification-to': -					    case 'x-confirm-reading-to': -    						$result[$id]->mdn_to = $string; -	    					break; -		    			case 'message-id': -			    			$result[$id]->messageID = $string; -				    		break; -					    case 'x-priority': -    						if (preg_match('/^(\d+)/', $string, $matches)) -	    						$result[$id]->priority = intval($matches[1]); -		    				break; -			    		default: -				    		if (strlen($field) > 2) -					    		$result[$id]->others[$field] = $string; -						    break; -    					} // end switch () -	    			} // end while () -			    } +                        switch ($field) { +                        case 'date'; +                            $result[$id]->date = $string; +                            $result[$id]->timestamp = $this->strToTime($string); +                            break; +                        case 'from': +                            $result[$id]->from = $string; +                            break; +                        case 'to': +                            $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string); +                            break; +                        case 'subject': +                            $result[$id]->subject = $string; +                            break; +                        case 'reply-to': +                            $result[$id]->replyto = $string; +                            break; +                        case 'cc': +                            $result[$id]->cc = $string; +                            break; +                        case 'bcc': +                            $result[$id]->bcc = $string; +                            break; +                        case 'content-transfer-encoding': +                            $result[$id]->encoding = $string; +                        break; +                        case 'content-type': +                            $ctype_parts = preg_split('/[; ]/', $string); +                            $result[$id]->ctype = array_shift($ctype_parts); +                            if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) { +                                $result[$id]->charset = $regs[1]; +                            } +                            break; +                        case 'in-reply-to': +                            $result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string); +                            break; +                        case 'references': +                            $result[$id]->references = $string; +                            break; +                        case 'return-receipt-to': +                        case 'disposition-notification-to': +                        case 'x-confirm-reading-to': +                            $result[$id]->mdn_to = $string; +                            break; +                        case 'message-id': +                            $result[$id]->messageID = $string; +                            break; +                        case 'x-priority': +                            if (preg_match('/^(\d+)/', $string, $matches)) { +                                $result[$id]->priority = intval($matches[1]); +                            } +                            break; +                        default: +                            if (strlen($field) > 2) { +                                $result[$id]->others[$field] = $string; +                            } +                            break; +                        } // end switch () +                    } // end while () +                } -    			// process flags -	    		if (!empty($flags_str)) { -		    		$flags_str = preg_replace('/[\\\"]/', '', $flags_str); -			    	$flags_a   = explode(' ', $flags_str); +                // process flags +                if (!empty($flags_str)) { +                    $flags_str = preg_replace('/[\\\"]/', '', $flags_str); +                    $flags_a   = explode(' ', $flags_str); -				    if (is_array($flags_a)) { -	    				foreach($flags_a as $flag) { -		    				$flag = strtoupper($flag); -			    			if ($flag == 'SEEN') { -				    		    $result[$id]->seen = true; -					    	} else if ($flag == 'DELETED') { -    						    $result[$id]->deleted = true; -	    					} else if ($flag == 'RECENT') { -		    				    $result[$id]->recent = true; -			    			} else if ($flag == 'ANSWERED') { -				    			$result[$id]->answered = true; -					    	} else if ($flag == '$FORWARDED') { -						    	$result[$id]->forwarded = true; -    						} else if ($flag == 'DRAFT') { -	    						$result[$id]->is_draft = true; -		    				} else if ($flag == '$MDNSENT') { -			    				$result[$id]->mdn_sent = true; -				    		} else if ($flag == 'FLAGGED') { -					    	         $result[$id]->flagged = true; -						    } -    					} -	    				$result[$id]->flags = $flags_a; -		    		} -			    } -    		} -	    } while (!$this->startsWith($line, $key, true)); +                    if (is_array($flags_a)) { +                        foreach($flags_a as $flag) { +                            $flag = strtoupper($flag); +                            if ($flag == 'SEEN') { +                                $result[$id]->seen = true; +                            } else if ($flag == 'DELETED') { +                                $result[$id]->deleted = true; +                            } else if ($flag == 'RECENT') { +                                $result[$id]->recent = true; +                            } else if ($flag == 'ANSWERED') { +                                $result[$id]->answered = true; +                            } else if ($flag == '$FORWARDED') { +                                $result[$id]->forwarded = true; +                            } else if ($flag == 'DRAFT') { +                                $result[$id]->is_draft = true; +                            } else if ($flag == '$MDNSENT') { +                                $result[$id]->mdn_sent = true; +                            } else if ($flag == 'FLAGGED') { +                                 $result[$id]->flagged = true; +                            } +                        } +                        $result[$id]->flags = $flags_a; +                    } +                } +            } +        } while (!$this->startsWith($line, $key, true)); -    	return $result; +        return $result;      }      function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='')      { -	    $a  = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); -	    if (is_array($a)) { -		    return array_shift($a); -	    } -	    return false; +        $a  = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); +        if (is_array($a)) { +            return array_shift($a); +        } +        return false;      }      function sortHeaders($a, $field, $flag)      { -	    if (empty($field)) { -	        $field = 'uid'; -	    } +        if (empty($field)) { +            $field = 'uid'; +        }          else { -    	    $field = strtolower($field); +            $field = strtolower($field);          } -	    if ($field == 'date' || $field == 'internaldate') { -	        $field = 'timestamp'; -	    } +        if ($field == 'date' || $field == 'internaldate') { +            $field = 'timestamp'; +        } -    	if (empty($flag)) { -	        $flag = 'ASC'; -	    } else { -        	$flag = strtoupper($flag); +        if (empty($flag)) { +            $flag = 'ASC'; +        } else { +            $flag = strtoupper($flag);          } -	    $c = count($a); -	    if ($c > 0) { -			// Strategy: -			// First, we'll create an "index" array. -			// Then, we'll use sort() on that array, -			// and use that to sort the main array. +        $c = count($a); +        if ($c > 0) { +            // Strategy: +            // First, we'll create an "index" array. +            // Then, we'll use sort() on that array, +            // and use that to sort the main array. -		    // create "index" array -		    $index = array(); -		    reset($a); -		    while (list($key, $val) = each($a)) { -			    if ($field == 'timestamp') { -				    $data = $this->strToTime($val->date); -				    if (!$data) { -					    $data = $val->timestamp; -            		} -			    } else { -				    $data = $val->$field; -				    if (is_string($data)) { -				        $data = str_replace('"', '', $data); -                	    if ($field == 'subject') { -				            $data = preg_replace('/^(Re: \s*|Fwd:\s*|Fw:\s*)+/i', '', $data); +            // create "index" array +            $index = array(); +            reset($a); +            while (list($key, $val) = each($a)) { +                if ($field == 'timestamp') { +                    $data = $this->strToTime($val->date); +                    if (!$data) { +                        $data = $val->timestamp; +                    } +                } else { +                    $data = $val->$field; +                    if (is_string($data)) { +                        $data = str_replace('"', '', $data); +                        if ($field == 'subject') { +                            $data = preg_replace('/^(Re: \s*|Fwd:\s*|Fw:\s*)+/i', '', $data);                          } -					    $data = strtoupper($data); -            		} -			    } -    			$index[$key] = $data; -	    	} - -		    // sort index -	    	if ($flag == 'ASC') { -		    	asort($index); -    		} else { -        	    arsort($index); -		    } - -    		// form new array based on index -	    	$result = array(); -		    reset($index); -    		while (list($key, $val) = each($index)) { -	    		$result[$key] = $a[$key]; -		    } -	    } +                        $data = strtoupper($data); +                    } +                } +                $index[$key] = $data; +            } -	    return $result; -    } +            // sort index +            if ($flag == 'ASC') { +                asort($index); +            } else { +                arsort($index); +            } -    function expunge($mailbox, $messages=NULL) -    { -	    if (!$this->select($mailbox)) { -            return false; +            // form new array based on index +            $result = array(); +            reset($index); +            while (list($key, $val) = each($index)) { +                $result[$key] = $a[$key]; +            }          } -        // Clear internal status cache -        unset($this->data['STATUS:'.$mailbox]); - -		if ($messages) -			$result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE); -		else -			$result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE); - -		if ($result == self::ERROR_OK) { -			$this->selected = ''; // state has changed, need to reselect -			return true; -		} - -	    return false; +        return $result;      } +      function modFlag($mailbox, $messages, $flag, $mod)      { -	    if ($mod != '+' && $mod != '-') { +        if ($mod != '+' && $mod != '-') {              $mod = '+'; -	    } +        } + +        if (!$this->select($mailbox)) { +            return false; +        } -	    if (!$this->select($mailbox)) { -	        return false; -	    } +        if (!$this->data['READ-WRITE']) { +            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE'); +            return false; +        }          // Clear internal status cache          if ($flag == 'SEEN') {              unset($this->data['STATUS:'.$mailbox]['UNSEEN']);          } -	    $flag   = $this->flags[strtoupper($flag)]; +        $flag   = $this->flags[strtoupper($flag)];          $result = $this->execute('UID STORE', array(              $this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"),              self::COMMAND_NORESPONSE); -	    return ($result == self::ERROR_OK); +        return ($result == self::ERROR_OK);      }      function flag($mailbox, $messages, $flag) { -	    return $this->modFlag($mailbox, $messages, $flag, '+'); +        return $this->modFlag($mailbox, $messages, $flag, '+');      }      function unflag($mailbox, $messages, $flag) { -	    return $this->modFlag($mailbox, $messages, $flag, '-'); +        return $this->modFlag($mailbox, $messages, $flag, '-');      }      function delete($mailbox, $messages) { -	    return $this->modFlag($mailbox, $messages, 'DELETED', '+'); +        return $this->modFlag($mailbox, $messages, 'DELETED', '+');      }      function copy($messages, $from, $to)      { -	    if (!$this->select($from)) { -	        return false; -	    } +        if (!$this->select($from)) { +            return false; +        }          // Clear internal status cache          unset($this->data['STATUS:'.$to]); @@ -1635,11 +1792,20 @@ class rcube_imap_generic              $this->compressMessageSet($messages), $this->escape($to)),              self::COMMAND_NORESPONSE); -	    return ($result == self::ERROR_OK); +        return ($result == self::ERROR_OK);      }      function move($messages, $from, $to)      { +        if (!$this->select($from)) { +            return false; +        } + +        if (!$this->data['READ-WRITE']) { +            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE'); +            return false; +        } +          $r = $this->copy($messages, $from, $to);          if ($r) { @@ -1656,76 +1822,76 @@ class rcube_imap_generic      // http://derickrethans.nl/files/phparch-php-variables-article.pdf      private function parseThread($str, $begin, $end, $root, $parent, $depth, &$depthmap, &$haschildren)      { -	    $node = array(); -	    if ($str[$begin] != '(') { -		    $stop = $begin + strspn($str, '1234567890', $begin, $end - $begin); -		    $msg = substr($str, $begin, $stop - $begin); -		    if ($msg == 0) -		        return $node; -		    if (is_null($root)) -			    $root = $msg; -		    $depthmap[$msg] = $depth; -		    $haschildren[$msg] = false; -		    if (!is_null($parent)) -			    $haschildren[$parent] = true; -		    if ($stop + 1 < $end) -			    $node[$msg] = $this->parseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren); -		    else -			    $node[$msg] = array(); -	    } else { -		    $off = $begin; -		    while ($off < $end) { -			    $start = $off; -    			$off++; -	    		$n = 1; -		    	while ($n > 0) { -			    	$p = strpos($str, ')', $off); -				    if ($p === false) { -					    error_log('Mismatched brackets parsing IMAP THREAD response:'); -    					error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20)); -	    				error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10)))); -		    			return $node; -			    	} -				    $p1 = strpos($str, '(', $off); -    				if ($p1 !== false && $p1 < $p) { -	    				$off = $p1 + 1; -		    			$n++; -			    	} else { -				    	$off = $p + 1; -					    $n--; -    				} -	    		} -		    	$node += $this->parseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren); -		    } -	    } +        $node = array(); +        if ($str[$begin] != '(') { +            $stop = $begin + strspn($str, '1234567890', $begin, $end - $begin); +            $msg = substr($str, $begin, $stop - $begin); +            if ($msg == 0) +                return $node; +            if (is_null($root)) +                $root = $msg; +            $depthmap[$msg] = $depth; +            $haschildren[$msg] = false; +            if (!is_null($parent)) +                $haschildren[$parent] = true; +            if ($stop + 1 < $end) +                $node[$msg] = $this->parseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren); +            else +                $node[$msg] = array(); +        } else { +            $off = $begin; +            while ($off < $end) { +                $start = $off; +                $off++; +                $n = 1; +                while ($n > 0) { +                    $p = strpos($str, ')', $off); +                    if ($p === false) { +                        error_log("Mismatched brackets parsing IMAP THREAD response:"); +                        error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20)); +                        error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10)))); +                        return $node; +                    } +                    $p1 = strpos($str, '(', $off); +                    if ($p1 !== false && $p1 < $p) { +                        $off = $p1 + 1; +                        $n++; +                    } else { +                        $off = $p + 1; +                        $n--; +                    } +                } +                $node += $this->parseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren); +            } +        } -	    return $node; +        return $node;      }      function thread($mailbox, $algorithm='REFERENCES', $criteria='', $encoding='US-ASCII')      {          $old_sel = $this->selected; -	    if (!$this->select($mailbox)) { -    		return false; -	    } +        if (!$this->select($mailbox)) { +            return false; +        }          // return empty result when folder is empty and we're just after SELECT          if ($old_sel != $mailbox && !$this->data['EXISTS']) {              return array(array(), array(), array()); -	    } +        } -    	$encoding  = $encoding ? trim($encoding) : 'US-ASCII'; -	    $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; -	    $criteria  = $criteria ? 'ALL '.trim($criteria) : 'ALL'; +        $encoding  = $encoding ? trim($encoding) : 'US-ASCII'; +        $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; +        $criteria  = $criteria ? 'ALL '.trim($criteria) : 'ALL';          $data      = '';          list($code, $response) = $this->execute('THREAD', array(              $algorithm, $encoding, $criteria)); -	    if ($code == self::ERROR_OK && preg_match('/^\* THREAD /i', $response)) { -	        // remove prefix and \r\n from raw response -	        $response    = str_replace("\r\n", '', substr($response, 9)); +        if ($code == self::ERROR_OK && preg_match('/^\* THREAD /i', $response)) { +            // remove prefix and \r\n from raw response +            $response    = str_replace("\r\n", '', substr($response, 9));              $depthmap    = array();              $haschildren = array(); @@ -1733,9 +1899,9 @@ class rcube_imap_generic                  null, null, 0, $depthmap, $haschildren);              return array($tree, $depthmap, $haschildren); -	    } +        } -	    return false; +        return false;      }      /** @@ -1752,9 +1918,9 @@ class rcube_imap_generic      {          $old_sel = $this->selected; -	    if (!$this->select($mailbox)) { -    		return false; -	    } +        if (!$this->select($mailbox)) { +            return false; +        }          // return empty result when folder is empty and we're just after SELECT          if ($old_sel != $mailbox && !$this->data['EXISTS']) { @@ -1762,7 +1928,7 @@ class rcube_imap_generic                  return array_combine($items, array_fill(0, count($items), 0));              else                  return array(); -	    } +        }          $esearch  = empty($items) ? false : $this->getCapability('ESEARCH');          $criteria = trim($criteria); @@ -1779,13 +1945,13 @@ class rcube_imap_generic              $params .= 'ALL';          } -	    list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH', -	        array($params)); +        list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH', +            array($params)); -	    if ($code == self::ERROR_OK) { -	        // remove prefix and \r\n from raw response +        if ($code == self::ERROR_OK) { +            // remove prefix and \r\n from raw response              $response = substr($response, $esearch ? 10 : 9); -	        $response = str_replace("\r\n", '', $response); +            $response = str_replace("\r\n", '', $response);              if ($esearch) {                  // Skip prefix: ... (TAG "A285") UID ... @@ -1803,29 +1969,33 @@ class rcube_imap_generic                  return $result;              } -	        else { +            else {                  $response = preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY);                  if (!empty($items)) {                      $result = array(); -                    if (in_array('COUNT', $items)) +                    if (in_array('COUNT', $items)) {                          $result['COUNT'] = count($response); -                    if (in_array('MIN', $items)) +                    } +                    if (in_array('MIN', $items)) {                          $result['MIN'] = !empty($response) ? min($response) : 0; -                    if (in_array('MAX', $items)) +                    } +                    if (in_array('MAX', $items)) {                          $result['MAX'] = !empty($response) ? max($response) : 0; -                    if (in_array('ALL', $items)) +                    } +                    if (in_array('ALL', $items)) {                          $result['ALL'] = $this->compressMessageSet($response, true); +                    }                      return $result;                  }                  else {                      return $response;                  } -	        } +            }          } -	    return false; +        return false;      }      /** @@ -1879,9 +2049,9 @@ class rcube_imap_generic      private function _listMailboxes($ref, $mailbox, $subscribed=false,          $status_opts=array(), $select_opts=array())      { -		if (!strlen($mailbox)) { -	        $mailbox = '*'; -	    } +        if (!strlen($mailbox)) { +            $mailbox = '*'; +        }          $args = array(); @@ -1913,7 +2083,7 @@ class rcube_imap_generic                      // Add to result array                      if (!$lstatus) { -           			    $folders[] = $mailbox; +                        $folders[] = $mailbox;                      }                      else {                          $folders[$mailbox] = array(); @@ -1937,406 +2107,378 @@ class rcube_imap_generic                          $folders[$mailbox][$name] = $value;                      }                  } -		    } +            }              return $folders;          } -    	return false; +        return false;      }      function fetchMIMEHeaders($mailbox, $id, $parts, $mime=true)      { -	    if (!$this->select($mailbox)) { -		    return false; -	    } +        if (!$this->select($mailbox)) { +            return false; +        } -    	$result = false; -	    $parts  = (array) $parts; -    	$key    = $this->nextTag(); -	    $peeks  = ''; -    	$idx    = 0; +        $result = false; +        $parts  = (array) $parts; +        $key    = $this->nextTag(); +        $peeks  = ''; +        $idx    = 0;          $type   = $mime ? 'MIME' : 'HEADER'; -	    // format request -	    foreach($parts as $part) -		    $peeks[] = "BODY.PEEK[$part.$type]"; +        // format request +        foreach($parts as $part) { +            $peeks[] = "BODY.PEEK[$part.$type]"; +        } -	    $request = "$key FETCH $id (" . implode(' ', $peeks) . ')'; +        $request = "$key FETCH $id (" . implode(' ', $peeks) . ')'; -	    // send request -	    if (!$this->putLine($request)) { +        // send request +        if (!$this->putLine($request)) {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); -	        return false; -	    } +            return false; +        } -	    do { -        	$line = $this->readLine(1024); -        	$line = $this->multLine($line); +        do { +            $line = $this->readLine(1024); +            $line = $this->multLine($line); -		    if (preg_match('/BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) { -			    $idx = $matches[1]; -    			$result[$idx] = preg_replace('/^(\* '.$id.' FETCH \()?\s*BODY\['.$idx.'\.'.$type.'\]\s+/', '', $line); -	    		$result[$idx] = trim($result[$idx], '"'); -	        	$result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B"); -    		} -	    } while (!$this->startsWith($line, $key, true)); +            if (preg_match('/BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) { +                $idx = $matches[1]; +                $result[$idx] = preg_replace('/^(\* '.$id.' FETCH \()?\s*BODY\['.$idx.'\.'.$type.'\]\s+/', '', $line); +                $result[$idx] = trim($result[$idx], '"'); +                $result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B"); +            } +        } while (!$this->startsWith($line, $key, true)); -	    return $result; +        return $result;      }      function fetchPartHeader($mailbox, $id, $is_uid=false, $part=NULL)      { -	    $part = empty($part) ? 'HEADER' : $part.'.MIME'; +        $part = empty($part) ? 'HEADER' : $part.'.MIME';          return $this->handlePartBody($mailbox, $id, $is_uid, $part);      }      function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL)      { -    	if (!$this->select($mailbox)) { +        if (!$this->select($mailbox)) {              return false;          } -	    switch ($encoding) { -		    case 'base64': -			    $mode = 1; -    		break; -	    	case 'quoted-printable': -		    	$mode = 2; -    		break; -	    	case 'x-uuencode': -		    case 'x-uue': -    		case 'uue': -	    	case 'uuencode': -		    	$mode = 3; -    		break; -	    	default: -		    	$mode = 0; -	    } +        switch ($encoding) { +        case 'base64': +            $mode = 1; +            break; +        case 'quoted-printable': +            $mode = 2; +            break; +        case 'x-uuencode': +        case 'x-uue': +        case 'uue': +        case 'uuencode': +            $mode = 3; +            break; +        default: +            $mode = 0; +        } -    	// format request -   		$reply_key = '* ' . $id; -		$key       = $this->nextTag(); -		$request   = $key . ($is_uid ? ' UID' : '') . " FETCH $id (BODY.PEEK[$part])"; +        // format request +        $reply_key = '* ' . $id; +        $key       = $this->nextTag(); +        $request   = $key . ($is_uid ? ' UID' : '') . " FETCH $id (BODY.PEEK[$part])"; -    	// send request -		if (!$this->putLine($request)) { +        // send request +        if (!$this->putLine($request)) {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); -		    return false; -   		} +            return false; +        } -   		// receive reply line -   		do { -       		$line = rtrim($this->readLine(1024)); -       		$a    = explode(' ', $line); -   		} while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH'); +        // receive reply line +        do { +            $line = rtrim($this->readLine(1024)); +            $a    = explode(' ', $line); +        } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH'); -   		$len    = strlen($line); -    	$result = false; +        $len    = strlen($line); +        $result = false; -		// handle empty "* X FETCH ()" response -    	if ($line[$len-1] == ')' && $line[$len-2] != '(') { -        	// one line response, get everything between first and last quotes -			if (substr($line, -4, 3) == 'NIL') { -				// NIL response -				$result = ''; -			} else { -			    $from = strpos($line, '"') + 1; -        		$to   = strrpos($line, '"'); -		        $len  = $to - $from; -				$result = substr($line, $from, $len); -			} +        // handle empty "* X FETCH ()" response +        if ($line[$len-1] == ')' && $line[$len-2] != '(') { +            // one line response, get everything between first and last quotes +            if (substr($line, -4, 3) == 'NIL') { +                // NIL response +                $result = ''; +            } else { +                $from = strpos($line, '"') + 1; +                $to   = strrpos($line, '"'); +                $len  = $to - $from; +                $result = substr($line, $from, $len); +            } -        	if ($mode == 1) -				$result = base64_decode($result); -			else if ($mode == 2) -				$result = quoted_printable_decode($result); -			else if ($mode == 3) -				$result = convert_uudecode($result); +            if ($mode == 1) { +                $result = base64_decode($result); +            } +            else if ($mode == 2) { +                $result = quoted_printable_decode($result); +            } +            else if ($mode == 3) { +                $result = convert_uudecode($result); +            } -    	} else if ($line[$len-1] == '}') { -	        // multi-line request, find sizes of content and receive that many bytes -        	$from     = strpos($line, '{') + 1; -	        $to       = strrpos($line, '}'); -        	$len      = $to - $from; -	        $sizeStr  = substr($line, $from, $len); -        	$bytes    = (int)$sizeStr; -			$prev	  = ''; +        } else if ($line[$len-1] == '}') { +            // multi-line request, find sizes of content and receive that many bytes +            $from     = strpos($line, '{') + 1; +            $to       = strrpos($line, '}'); +            $len      = $to - $from; +            $sizeStr  = substr($line, $from, $len); +            $bytes    = (int)$sizeStr; +            $prev     = ''; -        	while ($bytes > 0) { -    		    $line = $this->readLine(4096); +            while ($bytes > 0) { +                $line = $this->readLine(4096); -    		    if ($line === NULL) -    		        break; +                if ($line === NULL) { +                    break; +                } -            	$len  = strlen($line); +                $len  = strlen($line); -		        if ($len > $bytes) { -            		$line = substr($line, 0, $bytes); -					$len = strlen($line); -		        } -            	$bytes -= $len; +                if ($len > $bytes) { +                    $line = substr($line, 0, $bytes); +                    $len = strlen($line); +                } +                $bytes -= $len;                  // BASE64 -		        if ($mode == 1) { -					$line = rtrim($line, "\t\r\n\0\x0B"); -					// create chunks with proper length for base64 decoding -					$line = $prev.$line; -					$length = strlen($line); -					if ($length % 4) { -						$length = floor($length / 4) * 4; -						$prev = substr($line, $length); -						$line = substr($line, 0, $length); -					} -					else -						$prev = ''; -					$line = base64_decode($line); +                if ($mode == 1) { +                    $line = rtrim($line, "\t\r\n\0\x0B"); +                    // create chunks with proper length for base64 decoding +                    $line = $prev.$line; +                    $length = strlen($line); +                    if ($length % 4) { +                        $length = floor($length / 4) * 4; +                        $prev = substr($line, $length); +                        $line = substr($line, 0, $length); +                    } +                    else +                        $prev = ''; +                    $line = base64_decode($line);                  // QUOTED-PRINTABLE -				} else if ($mode == 2) { -					$line = rtrim($line, "\t\r\0\x0B"); +                } else if ($mode == 2) { +                    $line = rtrim($line, "\t\r\0\x0B");                      $line = quoted_printable_decode($line);                      // Remove NULL characters (#1486189)                      $line = str_replace("\x00", '', $line);                  // UUENCODE -				} else if ($mode == 3) { -					$line = rtrim($line, "\t\r\n\0\x0B"); -					if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) -						continue; +                } else if ($mode == 3) { +                    $line = rtrim($line, "\t\r\n\0\x0B"); +                    if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) +                        continue;                      $line = convert_uudecode($line);                  // default -				} else { -					$line = rtrim($line, "\t\r\n\0\x0B") . "\n"; -				} +                } else { +                    $line = rtrim($line, "\t\r\n\0\x0B") . "\n"; +                } -    			if ($file) -					fwrite($file, $line); -           		else if ($print) -					echo $line; -				else -					$result .= $line; -        	} -    	} +                if ($file) +                    fwrite($file, $line); +                else if ($print) +                    echo $line; +                else +                    $result .= $line; +            } +        }          // read in anything up until last line -		if (!$end) -			do { -        			$line = $this->readLine(1024); -			} while (!$this->startsWith($line, $key, true)); +        if (!$end) +            do { +                $line = $this->readLine(1024); +            } while (!$this->startsWith($line, $key, true)); -   		if ($result !== false) { -	    	if ($file) { -		    	fwrite($file, $result); -   			} else if ($print) { -    			echo $result; -	    	} else -		    	return $result; -   			return true; -   		} +        if ($result !== false) { +            if ($file) { +                fwrite($file, $result); +            } else if ($print) { +                echo $result; +            } else +                return $result; +            return true; +        } -	    return false; +        return false;      }      function createFolder($mailbox)      {          $result = $this->execute('CREATE', array($this->escape($mailbox)), -	        self::COMMAND_NORESPONSE); +            self::COMMAND_NORESPONSE); -	    return ($result == self::ERROR_OK); +        return ($result == self::ERROR_OK);      }      function renameFolder($from, $to)      {          $result = $this->execute('RENAME', array($this->escape($from), $this->escape($to)), -	        self::COMMAND_NORESPONSE); - -		return ($result == self::ERROR_OK); -    } - -    function deleteFolder($mailbox) -    { -        $result = $this->execute('DELETE', array($this->escape($mailbox)), -	        self::COMMAND_NORESPONSE); - -	    return ($result == self::ERROR_OK); -    } - -    function clearFolder($mailbox) -    { -	    $num_in_trash = $this->countMessages($mailbox); -	    if ($num_in_trash > 0) { -		    $this->delete($mailbox, '1:*'); -	    } -	    return ($this->expunge($mailbox) >= 0); -    } - -    function subscribe($mailbox) -    { -	    $result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)), -	        self::COMMAND_NORESPONSE); - -	    return ($result == self::ERROR_OK); -    } - -    function unsubscribe($mailbox) -    { -	    $result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)), -	        self::COMMAND_NORESPONSE); +            self::COMMAND_NORESPONSE); -	    return ($result == self::ERROR_OK); +        return ($result == self::ERROR_OK);      }      function append($mailbox, &$message)      { -	    if (!$mailbox) { -		    return false; -	    } +        if (!$mailbox) { +            return false; +        } -    	$message = str_replace("\r", '', $message); -	    $message = str_replace("\n", "\r\n", $message); +        $message = str_replace("\r", '', $message); +        $message = str_replace("\n", "\r\n", $message); -    	$len = strlen($message); -	    if (!$len) { -		    return false; -	    } +        $len = strlen($message); +        if (!$len) { +            return false; +        }          $key = $this->nextTag(); -	    $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox), +        $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox),              $len, ($this->prefs['literal+'] ? '+' : '')); -	    if ($this->putLine($request)) { +        if ($this->putLine($request)) {              // Don't wait when LITERAL+ is supported              if (!$this->prefs['literal+']) { -                $line = $this->readLine(512); +                $line = $this->readReply(); -    		    if ($line[0] != '+') { -	    		    $this->parseResult($line, 'APPEND: '); -			        return false; -    		    } +                if ($line[0] != '+') { +                    $this->parseResult($line, 'APPEND: '); +                    return false; +                }              } -	    	if (!$this->putLine($message)) { +            if (!$this->putLine($message)) {                  return false;              } -		    do { -			    $line = $this->readLine(); -    		} while (!$this->startsWith($line, $key, true, true)); +            do { +                $line = $this->readLine(); +            } while (!$this->startsWith($line, $key, true, true));              // Clear internal status cache              unset($this->data['STATUS:'.$mailbox]); -    		return ($this->parseResult($line, 'APPEND: ') == self::ERROR_OK); -	    } +            return ($this->parseResult($line, 'APPEND: ') == self::ERROR_OK); +        }          else {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");          } -	    return false; +        return false;      }      function appendFromFile($mailbox, $path, $headers=null)      { -	    if (!$mailbox) { -	        return false; -	    } +        if (!$mailbox) { +            return false; +        } -	    // open message file -	    $in_fp = false; -	    if (file_exists(realpath($path))) { -		    $in_fp = fopen($path, 'r'); -	    } -	    if (!$in_fp) { -		    $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading"); -		    return false; -	    } +        // open message file +        $in_fp = false; +        if (file_exists(realpath($path))) { +            $in_fp = fopen($path, 'r'); +        } +        if (!$in_fp) { +            $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading"); +            return false; +        }          $body_separator = "\r\n\r\n"; -	    $len = filesize($path); +        $len = filesize($path); -	    if (!$len) { -		    return false; -	    } +        if (!$len) { +            return false; +        }          if ($headers) {              $headers = preg_replace('/[\r\n]+$/', '', $headers);              $len += strlen($headers) + strlen($body_separator);          } -    	// send APPEND command -    	$key = $this->nextTag(); -	    $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox), +        // send APPEND command +        $key = $this->nextTag(); +        $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox),              $len, ($this->prefs['literal+'] ? '+' : '')); -	    if ($this->putLine($request)) { +        if ($this->putLine($request)) {              // Don't wait when LITERAL+ is supported              if (!$this->prefs['literal+']) { -    		    $line = $this->readLine(512); +                $line = $this->readReply(); -	    	    if ($line[0] != '+') { -		    	    $this->parseResult($line, 'APPEND: '); -			        return false; -		        } +                if ($line[0] != '+') { +                    $this->parseResult($line, 'APPEND: '); +                    return false; +                }              }              // send headers with body separator              if ($headers) { -			    $this->putLine($headers . $body_separator, false); +                $this->putLine($headers . $body_separator, false);              } -		    // send file -		    while (!feof($in_fp) && $this->fp) { -			    $buffer = fgets($in_fp, 4096); -			    $this->putLine($buffer, false); -		    } -		    fclose($in_fp); +            // send file +            while (!feof($in_fp) && $this->fp) { +                $buffer = fgets($in_fp, 4096); +                $this->putLine($buffer, false); +            } +            fclose($in_fp); -		    if (!$this->putLine('')) { // \r\n +            if (!$this->putLine('')) { // \r\n                  return false;              } -		    // read response -		    do { -			    $line = $this->readLine(); -		    } while (!$this->startsWith($line, $key, true, true)); +            // read response +            do { +                $line = $this->readLine(); +            } while (!$this->startsWith($line, $key, true, true));              // Clear internal status cache              unset($this->data['STATUS:'.$mailbox]); -		    return ($this->parseResult($line, 'APPEND: ') == self::ERROR_OK); -	    } +            return ($this->parseResult($line, 'APPEND: ') == self::ERROR_OK); +        }          else {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");          } -	    return false; +        return false;      }      function fetchStructureString($mailbox, $id, $is_uid=false)      { -	    if (!$this->select($mailbox)) { +        if (!$this->select($mailbox)) {              return false;          } -		$key = $this->nextTag(); -    	$result = false; +        $key = $this->nextTag(); +        $result = false;          $command = $key . ($is_uid ? ' UID' : '') ." FETCH $id (BODYSTRUCTURE)"; -		if ($this->putLine($command)) { -			do { -				$line = $this->readLine(5000); -				$line = $this->multLine($line, true); -				if (!preg_match("/^$key /", $line)) -					$result .= $line; -			} while (!$this->startsWith($line, $key, true, true)); +        if ($this->putLine($command)) { +            do { +                $line = $this->readLine(5000); +                $line = $this->multLine($line, true); +                if (!preg_match("/^$key /", $line)) +                    $result .= $line; +            } while (!$this->startsWith($line, $key, true, true)); -			$result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -1)); -		} +            $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -1)); +        }          else {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $command");          } -    	return $result; +        return $result;      }      function getQuota() @@ -2347,49 +2489,50 @@ class rcube_imap_generic           * QUOTA user/rchijiiwa1 (STORAGE 654 9765)           * OK Completed           */ -	    $result      = false; -	    $quota_lines = array(); -	    $key         = $this->nextTag(); +        $result      = false; +        $quota_lines = array(); +        $key         = $this->nextTag();          $command     = $key . ' GETQUOTAROOT INBOX'; -	    // get line(s) containing quota info -	    if ($this->putLine($command)) { -		    do { -			    $line = rtrim($this->readLine(5000)); -			    if (preg_match('/^\* QUOTA /', $line)) { -				    $quota_lines[] = $line; -        		} -		    } while (!$this->startsWith($line, $key, true, true)); -	    } +        // get line(s) containing quota info +        if ($this->putLine($command)) { +            do { +                $line = rtrim($this->readLine(5000)); +                if (preg_match('/^\* QUOTA /', $line)) { +                    $quota_lines[] = $line; +                } +            } while (!$this->startsWith($line, $key, true, true)); +        }          else {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $command");          } -	    // return false if not found, parse if found -	    $min_free = PHP_INT_MAX; -	    foreach ($quota_lines as $key => $quota_line) { -		    $quota_line   = str_replace(array('(', ')'), '', $quota_line); -		    $parts        = explode(' ', $quota_line); -		    $storage_part = array_search('STORAGE', $parts); +        // return false if not found, parse if found +        $min_free = PHP_INT_MAX; +        foreach ($quota_lines as $key => $quota_line) { +            $quota_line   = str_replace(array('(', ')'), '', $quota_line); +            $parts        = explode(' ', $quota_line); +            $storage_part = array_search('STORAGE', $parts); -		    if (!$storage_part) +            if (!$storage_part) {                  continue; +            } -		    $used  = intval($parts[$storage_part+1]); -		    $total = intval($parts[$storage_part+2]); -		    $free  = $total - $used; +            $used  = intval($parts[$storage_part+1]); +            $total = intval($parts[$storage_part+2]); +            $free  = $total - $used; -		    // return lowest available space from all quotas -		    if ($free < $min_free) { -		        $min_free          = $free; -			    $result['used']    = $used; -			    $result['total']   = $total; -			    $result['percent'] = min(100, round(($used/max(1,$total))*100)); -			    $result['free']    = 100 - $result['percent']; -		    } -	    } +            // return lowest available space from all quotas +            if ($free < $min_free) { +                $min_free          = $free; +                $result['used']    = $used; +                $result['total']   = $total; +                $result['percent'] = min(100, round(($used/max(1,$total))*100)); +                $result['free']    = 100 - $result['percent']; +            } +        } -	    return $result; +        return $result;      }      /** @@ -2414,7 +2557,7 @@ class rcube_imap_generic              $this->escape($mailbox), $this->escape($user), strtolower($acl)),              self::COMMAND_NORESPONSE); -	    return ($result == self::ERROR_OK); +        return ($result == self::ERROR_OK);      }      /** @@ -2434,7 +2577,7 @@ class rcube_imap_generic              $this->escape($mailbox), $this->escape($user)),              self::COMMAND_NORESPONSE); -	    return ($result == self::ERROR_OK); +        return ($result == self::ERROR_OK);      }      /** @@ -2554,11 +2697,12 @@ class rcube_imap_generic          }          foreach ($entries as $name => $value) { -            if ($value === null) +            if ($value === null) {                  $value = 'NIL'; -            else +            } +            else {                  $value = sprintf("{%d}\r\n%s", strlen($value), $value); - +            }              $entries[$name] = $this->escape($name) . ' ' . $value;          } @@ -2583,16 +2727,18 @@ class rcube_imap_generic       */      function deleteMetadata($mailbox, $entries)      { -        if (!is_array($entries) && !empty($entries)) +        if (!is_array($entries) && !empty($entries)) {              $entries = explode(' ', $entries); +        }          if (empty($entries)) {              $this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");              return false;          } -        foreach ($entries as $entry) +        foreach ($entries as $entry) {              $data[$entry] = NULL; +        }          return $this->setMetadata($mailbox, $data);      } @@ -2628,13 +2774,16 @@ class rcube_imap_generic              $options = array_change_key_case($options, CASE_UPPER);              $opts = array(); -            if (!empty($options['MAXSIZE'])) +            if (!empty($options['MAXSIZE'])) {                  $opts[] = 'MAXSIZE '.intval($options['MAXSIZE']); -            if (!empty($options['DEPTH'])) +            } +            if (!empty($options['DEPTH'])) {                  $opts[] = 'DEPTH '.intval($options['DEPTH']); +            } -            if ($opts) +            if ($opts) {                  $optlist = '(' . implode(' ', $opts) . ')'; +            }          }          $optlist .= ($optlist ? ' ' : '') . $entlist; @@ -2701,10 +2850,12 @@ class rcube_imap_generic              $attr  = $entry[1];              $value = $entry[2]; -            if ($value === null) +            if ($value === null) {                  $value = 'NIL'; -            else +            } +            else {                  $value = sprintf("{%d}\r\n%s", strlen($value), $value); +            }              $entries[] = sprintf('%s (%s %s)',                  $this->escape($name), $this->escape($attr), $value); @@ -2796,10 +2947,12 @@ class rcube_imap_generic                          for ($x=0, $len=count($attribs); $x<$len;) {                              $attr  = $attribs[$x++];                              $value = $attribs[$x++]; -                            if ($attr == 'value.priv') +                            if ($attr == 'value.priv') {                                  $res['/private' . $entry] = $value; -                            else if ($attr == 'value.shared') +                            } +                            else if ($attr == 'value.shared') {                                  $res['/shared' . $entry] = $value; +                            }                          }                      }                      $last_entry = $entry; @@ -2847,43 +3000,45 @@ class rcube_imap_generic          $noresp   = ($options & self::COMMAND_NORESPONSE);          $response = $noresp ? null : ''; -        if (!empty($arguments)) +        if (!empty($arguments)) {              $query .= ' ' . implode(' ', $arguments); +        }          // Send command -	    if (!$this->putLineC($query)) { +        if (!$this->putLineC($query)) {              $this->setError(self::ERROR_COMMAND, "Unable to send command: $query"); -		    return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, ''); -	    } +            return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, ''); +        }          // Parse response -    	do { -	    	$line = $this->readLine(4096); -	    	if ($response !== null) -	    	    $response .= $line; -    	} while (!$this->startsWith($line, $tag . ' ', true, true)); +        do { +            $line = $this->readLine(4096); +            if ($response !== null) { +                $response .= $line; +            } +        } while (!$this->startsWith($line, $tag . ' ', true, true)); -	    $code = $this->parseResult($line, $command . ': '); +        $code = $this->parseResult($line, $command . ': ');          // Remove last line from response -    	if ($response) { -    	    $line_len = min(strlen($response), strlen($line) + 2); +        if ($response) { +            $line_len = min(strlen($response), strlen($line) + 2);              $response = substr($response, 0, -$line_len);          } -   	    // optional CAPABILITY response -	    if (($options & self::COMMAND_CAPABILITY) && $code == self::ERROR_OK +        // optional CAPABILITY response +        if (($options & self::COMMAND_CAPABILITY) && $code == self::ERROR_OK              && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)          ) { -		    $this->parseCapability($matches[1], true); -	    } +            $this->parseCapability($matches[1], true); +        } -        // return last line only (without command tag and result) +        // return last line only (without command tag, result and response code)          if ($line && ($options & self::COMMAND_LASTLINE)) { -            $response = preg_replace("/^$tag (OK|NO|BAD|BYE|PREAUTH)?\s*/i", '', trim($line)); +            $response = preg_replace("/^$tag (OK|NO|BAD|BYE|PREAUTH)?\s*(\[[a-z-]+\])?\s*/i", '', trim($line));          } -	    return $noresp ? $code : array($code, $response); +        return $noresp ? $code : array($code, $response);      }      /** @@ -2917,7 +3072,7 @@ class rcube_imap_generic                  $result[] = substr($str, $epos + 3, $bytes);                  // Advance the string                  $str = substr($str, $epos + 3 + $bytes); -            break; +                break;              // Quoted string              case '"': @@ -2939,17 +3094,17 @@ class rcube_imap_generic                  // we need to strip slashes for a quoted string                  $result[] = stripslashes(substr($str, 1, $pos - 1));                  $str      = substr($str, $pos + 1); -            break; +                break;              // Parenthesized list              case '(':                  $str = substr($str, 1);                  $result[] = self::tokenizeResponse($str); -            break; +                break;              case ')':                  $str = substr($str, 1);                  return $result; -            break; +                break;              // String atom, number, NIL, *, %              default: @@ -2968,7 +3123,7 @@ class rcube_imap_generic                      $result[] = $m[1] == 'NIL' ? NULL : $m[1];                      $str = substr($str, strlen($m[1]));                  } -            break; +                break;              }          } @@ -2977,12 +3132,14 @@ class rcube_imap_generic      private function _xor($string, $string2)      { -	    $result = ''; -	    $size = strlen($string); -	    for ($i=0; $i<$size; $i++) { -    		$result .= chr(ord($string[$i]) ^ ord($string2[$i])); -	    } -	    return $result; +        $result = ''; +        $size   = strlen($string); + +        for ($i=0; $i<$size; $i++) { +            $result .= chr(ord($string[$i]) ^ ord($string2[$i])); +        } + +        return $result;      }      /** @@ -2994,31 +3151,33 @@ class rcube_imap_generic       */      private function strToTime($date)      { -	    // support non-standard "GMTXXXX" literal -	    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); +        // support non-standard "GMTXXXX" literal +        $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);          // if date parsing fails, we have a date in non-rfc format. -	    // remove token from the end and try again -	    while ((($ts = @strtotime($date))===false) || ($ts < 0)) { -	        $d = explode(' ', $date); -		    array_pop($d); -		    if (!$d) break; -		    $date = implode(' ', $d); -	    } +        // remove token from the end and try again +        while ((($ts = @strtotime($date))===false) || ($ts < 0)) { +            $d = explode(' ', $date); +            array_pop($d); +            if (!$d) { +                break; +            } +            $date = implode(' ', $d); +        } -	    $ts = (int) $ts; +        $ts = (int) $ts; -	    return $ts < 0 ? 0 : $ts; +        return $ts < 0 ? 0 : $ts;      }      private function splitHeaderLine($string)      { -	    $pos = strpos($string, ':'); -	    if ($pos>0) { -		    $res[0] = substr($string, 0, $pos); -		    $res[1] = trim(substr($string, $pos+1)); -		    return $res; -	    } -    	return $string; +        $pos = strpos($string, ':'); +        if ($pos>0) { +            $res[0] = substr($string, 0, $pos); +            $res[1] = trim(substr($string, $pos+1)); +            return $res; +        } +        return $string;      }      private function parseCapability($str, $trusted=false) @@ -3046,17 +3205,15 @@ class rcube_imap_generic       */      static function escape($string)      { -        // NIL          if ($string === null) {              return 'NIL';          } -        // empty string          else if ($string === '') {              return '""';          } -        // string: special chars: SP, CTL, (, ), {, %, *, ", \, ]          else if (preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5C\x5D\x7F]+)/', $string)) { -	        return '"' . strtr($string, array('"'=>'\\"', '\\' => '\\\\')) . '"'; +            // string: special chars: SP, CTL, (, ), {, %, *, ", \, ] +            return '"' . strtr($string, array('"'=>'\\"', '\\' => '\\\\')) . '"';          }          // atom @@ -3065,7 +3222,7 @@ class rcube_imap_generic      static function unEscape($string)      { -	    return strtr($string, array('\\"'=>'"', '\\\\' => '\\')); +        return strtr($string, array('\\"'=>'"', '\\\\' => '\\'));      }  } diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index 790f8d9bd..b6c865d1c 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -426,7 +426,7 @@ class rcube_message                          $mail_part->type = 'content';                          $this->parts[] = $mail_part;                      } -           +                      // list as attachment as well                      if (!empty($mail_part->filename))                          $this->attachments[] = $mail_part; @@ -473,6 +473,10 @@ class rcube_message                      // attachment encapsulated within message/rfc822 part needs further decoding (#1486743)                      else if ($part_orig_mimetype == 'message/rfc822') {                          $this->parse_structure($mail_part, true); + +                        // list as attachment as well (mostly .eml) +                        if (!empty($mail_part->filename)) +                            $this->attachments[] = $mail_part;                      }                      // is a regular attachment (content-type name regexp according to RFC4288.4.2)                      else if (preg_match('/^[a-z0-9!#$&.+^_-]+\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) { diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php index 8a9eff4f5..d6ea3dce2 100755 --- a/program/include/rcube_template.php +++ b/program/include/rcube_template.php @@ -1045,7 +1045,7 @@ class rcube_template extends rcube_html_page      private function login_form($attrib)      {          $default_host = $this->config['default_host']; -        $attrib['autocomplete'] = $this->config['login_autocomplete'] ? null : 'off'; +        $autocomplete = (int) $this->config['login_autocomplete'];          $_SESSION['temp'] = true; @@ -1054,11 +1054,18 @@ class rcube_template extends rcube_html_page          if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))              $url = $_SERVER['QUERY_STRING']; -        $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser') + $attrib); -        $input_pass   = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd') + $attrib); +        // set atocomplete attribute +        $user_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off'); +        $host_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off'); +        $pass_attrib = $autocomplete > 1 ? array() : array('autocomplete' => 'off'); +          $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));          $input_tzone  = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));          $input_url    = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url)); +        $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser') +            + $attrib + $user_attrib); +        $input_pass   = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd') +            + $attrib + $pass_attrib);          $input_host   = null;          if (is_array($default_host) && count($default_host) > 1) { @@ -1080,7 +1087,8 @@ class rcube_template extends rcube_html_page                  'name' => '_host', 'id' => 'rcmloginhost', 'value' => $host) + $attrib);          }          else if (empty($default_host)) { -            $input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost') + $attrib); +            $input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost') +                + $attrib + $host_attrib);          }          $form_name  = !empty($attrib['form']) ? $attrib['form'] : 'form'; @@ -1090,7 +1098,7 @@ class rcube_template extends rcube_html_page          $table = new html_table(array('cols' => 2));          $table->add('title', html::label('rcmloginuser', Q(rcube_label('username')))); -        $table->add(null, $input_user->show(get_input_value('_user', RCUBE_INPUT_POST))); +        $table->add(null, $input_user->show(get_input_value('_user', RCUBE_INPUT_GPC)));          $table->add('title', html::label('rcmloginpwd', Q(rcube_label('password'))));          $table->add(null, $input_pass->show()); @@ -1098,7 +1106,7 @@ class rcube_template extends rcube_html_page          // add host selection row          if (is_object($input_host) && !$hide_host) {              $table->add('title', html::label('rcmloginhost', Q(rcube_label('server')))); -            $table->add(null, $input_host->show(get_input_value('_host', RCUBE_INPUT_POST))); +            $table->add(null, $input_host->show(get_input_value('_host', RCUBE_INPUT_GPC)));          }          $out = $input_action->show(); diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php index e4506cff7..ee6db77cc 100644 --- a/program/include/rcube_user.php +++ b/program/include/rcube_user.php @@ -358,13 +358,17 @@ class rcube_user      {          $dbh = rcmail::get_instance()->get_dbh(); +        // use BINARY (case-sensitive) comparison on MySQL, other engines are case-sensitive +        $prefix = preg_match('/^mysql/', $dbh->db_provider) ? 'BINARY ' : ''; +          // query for matching user name          $query = "SELECT * FROM ".get_table_name('users')." WHERE mail_host = ? AND %s = ?"; -        $sql_result = $dbh->query(sprintf($query, 'username'), $host, $user); + +        $sql_result = $dbh->query(sprintf($query, $prefix.'username'), $host, $user);          // query for matching alias          if (!($sql_arr = $dbh->fetch_assoc($sql_result))) { -            $sql_result = $dbh->query(sprintf($query, 'alias'), $host, $user); +            $sql_result = $dbh->query(sprintf($query, $prefix.'alias'), $host, $user);              $sql_arr = $dbh->fetch_assoc($sql_result);          }  | 
