From 90f81a6c8de5aecfa36c54cc5260d25ba883aa51 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Wed, 08 Dec 2010 07:52:04 -0500
Subject: [PATCH] - Better support for READ-ONLY and NOPERM responses handling (#1487083) - Add confirmation message on purge/expunge commands response - Fix CLOSE was called on unselected mailbox

---
 CHANGELOG                               |    2 
 program/steps/settings/folders.inc      |   15 +-
 program/include/main.inc                |   32 ++++++
 program/steps/mail/copy.inc             |    4 
 program/steps/mail/move_del.inc         |    4 
 program/steps/mail/list.inc             |    3 
 program/steps/mail/search.inc           |    3 
 program/include/rcube_imap.php          |   72 +++++++++++++
 program/steps/mail/folders.inc          |   82 ++++++++-------
 program/localization/en_US/messages.inc |    3 
 program/localization/pl_PL/messages.inc |    3 
 program/steps/mail/mark.inc             |    2 
 program/include/rcube_imap_generic.php  |   62 ++++++++++--
 13 files changed, 220 insertions(+), 67 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 8d51add..70b00a7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,6 +16,8 @@
 - Improve performance of moving or copying of all messages in a folder
 - Fix plaintext versions of HTML messages don't contain placeholders for emotions (#1485206)
 - Improve performance of folder rename and delete actions
+- Better support for READ-ONLY and NOPERM responses handling (#1487083)
+- Add confirmation message on purge/expunge command response
 
 RELEASE 0.5-BETA
 ----------------
diff --git a/program/include/main.inc b/program/include/main.inc
index ad0bccd..160c835 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -1616,6 +1616,38 @@
 
 
 /**
+ * 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
  *
  * @param string Editor mode
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index f0d1119..d1947c4 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -100,6 +100,16 @@
         '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
@@ -220,7 +230,51 @@
      */
     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;
     }
 
 
@@ -295,9 +349,9 @@
      * @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);
 
@@ -2769,6 +2823,18 @@
         else
             $a_uids = NULL;
 
+        // 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();
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index 10bc6d0..b2346ba 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -85,6 +85,8 @@
 {
     public $error;
     public $errornum;
+    public $result;
+    public $resultcode;
     public $data = array();
     public $flags = array(
         'SEEN'     => '\\Seen',
@@ -112,8 +114,9 @@
     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;
@@ -302,7 +305,7 @@
             $str = trim($matches[2]);
 
 		    if ($res == 'OK') {
-			    return $this->errornum = self::ERROR_OK;
+			    $this->errornum = self::ERROR_OK;
 		    } else if ($res == 'NO') {
                 $this->errornum = self::ERROR_NO;
 		    } else if ($res == 'BAD') {
@@ -313,15 +316,29 @@
 			    $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;
     }
 
-    private function setError($code, $msg='')
+    function setError($code, $msg='')
     {
         $this->errornum = $code;
         $this->error    = $msg;
@@ -838,6 +855,8 @@
 			    }
             }
 
+            $this->data['READ-WRITE'] = $this->resultcode != 'READ-ONLY';
+
 		    $this->selected = $mailbox;
 			return true;
 		}
@@ -903,6 +922,11 @@
     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;
         }
 
@@ -1001,11 +1025,15 @@
     {
 	    $num_in_trash = $this->countMessages($mailbox);
 	    if ($num_in_trash > 0) {
-		    $this->delete($mailbox, '1:*');
+		    $res = $this->delete($mailbox, '1:*');
 	    }
 
-        $res = $this->close();
-//	    $res = $this->expunge($mailbox);
+        if ($res) {
+            if ($this->selected == $mailbox)
+                $res = $this->close();
+            else
+    	        $res = $this->expunge($mailbox);
+        }
 
 	    return $res;
     }
@@ -1715,6 +1743,11 @@
 	        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']);
@@ -1758,6 +1791,15 @@
 
     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) {
@@ -2963,9 +3005,9 @@
 		    $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);
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
index d47f555..df78b0f 100644
--- a/program/localization/en_US/messages.inc
+++ b/program/localization/en_US/messages.inc
@@ -24,6 +24,8 @@
 $messages['imaperror'] = 'Connection to IMAP server failed';
 $messages['servererror'] = 'Server Error!';
 $messages['servererrormsg'] = 'Server Error: $msg';
+$messages['errorreadonly'] = 'Unable to perform operation. Folder is read-only';
+$messages['errornoperm'] = 'Unable to perform operation. Permission denied';
 $messages['invalidrequest'] = 'Invalid request! No data was saved.';
 $messages['nomessagesfound'] = 'No messages found in this mailbox';
 $messages['loggedout'] = 'You have successfully terminated the session. Good bye!';
@@ -81,6 +83,7 @@
 $messages['foldersubscribed'] = 'Folder successfully subscribed';
 $messages['folderunsubscribed'] = 'Folder successfully unsubscribed';
 $messages['folderpurged'] = 'Folder successfully purged';
+$messages['folderexpunged'] = 'Folder successfully emptied';
 $messages['deletedsuccessfully'] = 'Successfully deleted';
 $messages['converting'] = 'Removing formatting...';
 $messages['messageopenerror'] = 'Could not load message from server';
diff --git a/program/localization/pl_PL/messages.inc b/program/localization/pl_PL/messages.inc
index 128163a..d3c205b 100644
--- a/program/localization/pl_PL/messages.inc
+++ b/program/localization/pl_PL/messages.inc
@@ -135,9 +135,12 @@
 $messages['foldersubscribed'] = 'Folder pomyślnie zasubskrybowany';
 $messages['folderunsubscribed'] = 'Folder pomyślnie odsubskrybowany';
 $messages['folderpurged'] = 'Folder pomyślnie wyczyszczony';
+$messages['folderexpunged'] = 'Folder pomyślnie opróżniony';
 $messages['namecannotbeempty'] = 'Nazwa nie może być pusta';
 $messages['nametoolong'] = 'Name jest zbyt długa';
 $messages['folderupdated'] = 'Folder pomyślnie zaktualizowany';
 $messages['foldercreated'] = 'Folder pomyślnie utworzony';
+$messages['errorreadonly'] = 'Nie można wykonać operacji. Folder tylko do odczytu';
+$messages['errornoperm'] = 'Nie można wykonać operacji. Brak uprawnień';
 
 ?>
diff --git a/program/steps/mail/copy.inc b/program/steps/mail/copy.inc
index 4cd51d8..8a7c591 100644
--- a/program/steps/mail/copy.inc
+++ b/program/steps/mail/copy.inc
@@ -33,7 +33,7 @@
 
     if (!$copied) {
         // send error message
-        $OUTPUT->show_message('errorcopying', 'error');
+        rcmail_display_server_error('errorcopying');
         $OUTPUT->send();
         exit;
     }
@@ -52,5 +52,3 @@
 
 // send response
 $OUTPUT->send();
-
-
diff --git a/program/steps/mail/folders.inc b/program/steps/mail/folders.inc
index a74e616..3b96dc2 100644
--- a/program/steps/mail/folders.inc
+++ b/program/steps/mail/folders.inc
@@ -20,55 +20,61 @@
 
 // only process ajax requests
 if (!$OUTPUT->ajax_call)
-  return;
+    return;
 
 $mbox = get_input_value('_mbox', RCUBE_INPUT_POST, true);
 
 // send EXPUNGE command
-if ($RCMAIL->action=='expunge')
-{
-  $success = $IMAP->expunge($mbox);
+if ($RCMAIL->action == 'expunge') {
 
-  // reload message list if current mailbox
-  if ($success && !empty($_REQUEST['_reload']))
-  {
-    $OUTPUT->command('set_quota', rcmail_quota_content());
-    $OUTPUT->command('message_list.clear');
-    $RCMAIL->action = 'list';
-    return;
-  }
-  else
-    $commands = "// expunged: $success\n";
+    $success = $IMAP->expunge($mbox);
+
+    // reload message list if current mailbox
+    if ($success) {
+        $OUTPUT->show_message('folderexpunged', 'confirmation');
+
+        if (!empty($_REQUEST['_reload'])) {
+            $OUTPUT->command('set_quota', rcmail_quota_content());
+            $OUTPUT->command('message_list.clear');
+            $RCMAIL->action = 'list';
+            return;
+        }
+    }
+    else {
+        rcmail_display_server_error();
+    }
 }
 
 // clear mailbox
-else if ($RCMAIL->action=='purge')
+else if ($RCMAIL->action == 'purge')
 {
-  $delimiter = $IMAP->get_hierarchy_delimiter();
-  $trash_regexp = '/^' . preg_quote($CONFIG['trash_mbox'] . $delimiter, '/') . '/';
-  $junk_regexp = '/^' . preg_quote($CONFIG['junk_mbox'] . $delimiter, '/') . '/';
+    $delimiter = $IMAP->get_hierarchy_delimiter();
+    $trash_regexp = '/^' . preg_quote($CONFIG['trash_mbox'] . $delimiter, '/') . '/';
+    $junk_regexp  = '/^' . preg_quote($CONFIG['junk_mbox'] . $delimiter, '/') . '/';
 
-  // we should only be purging trash and junk (or their subfolders)
-  if ($mbox == $CONFIG['trash_mbox'] || $mbox == $CONFIG['junk_mbox']
-    || preg_match($trash_regexp, $mbox) || preg_match($junk_regexp, $mbox))
-  {
-    $success = $IMAP->clear_mailbox($mbox);
+    // we should only be purging trash and junk (or their subfolders)
+    if ($mbox == $CONFIG['trash_mbox'] || $mbox == $CONFIG['junk_mbox']
+        || preg_match($trash_regexp, $mbox) || preg_match($junk_regexp, $mbox)
+    ) {
+        $success = $IMAP->clear_mailbox($mbox);
 
-    if ($success && !empty($_REQUEST['_reload']))
-    {
-      $OUTPUT->set_env('messagecount', 0);
-      $OUTPUT->set_env('pagecount', 0);
-      $OUTPUT->command('message_list.clear');
-      $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text());
-      $OUTPUT->command('set_unread_count', $mbox, 0);
-      $OUTPUT->command('set_quota', rcmail_quota_content());
-      rcmail_set_unseen_count($mbox, 0);
+        if ($success) {
+            $OUTPUT->show_message('folderpurged', 'confirmation');
+
+            if (!empty($_REQUEST['_reload'])) {
+                $OUTPUT->set_env('messagecount', 0);
+                $OUTPUT->set_env('pagecount', 0);
+                $OUTPUT->command('message_list.clear');
+                $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text());
+                $OUTPUT->command('set_unread_count', $mbox, 0);
+                $OUTPUT->command('set_quota', rcmail_quota_content());
+                rcmail_set_unseen_count($mbox, 0);
+            }
+        }
+        else {
+            rcmail_display_server_error();
+        }
     }
-    else
-      $commands = "// purged: $success";
-  }
 }
 
-$OUTPUT->send($commands);
-
-
+$OUTPUT->send();
diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc
index 6353be7..7e6d294 100644
--- a/program/steps/mail/list.inc
+++ b/program/steps/mail/list.inc
@@ -106,8 +106,7 @@
 else {
   // handle IMAP errors (e.g. #1486905)
   if ($err_code = $IMAP->get_error_code()) {
-    $err_str = $IMAP->get_error_str();
-    $OUTPUT->show_message('servererrormsg', 'error', array('msg' => $err_str));
+    rcmail_display_server_error();
   }
   else if ($search_request)
     $OUTPUT->show_message('searchnomatch', 'notice');
diff --git a/program/steps/mail/mark.inc b/program/steps/mail/mark.inc
index 65a8fc6..1ff4407 100644
--- a/program/steps/mail/mark.inc
+++ b/program/steps/mail/mark.inc
@@ -47,7 +47,7 @@
     // send error message
     if ($_POST['_from'] != 'show')
       $OUTPUT->command('list_mailbox');
-    $OUTPUT->show_message('errormarking', 'error');
+    rcmail_display_server_error('errormarking');
     $OUTPUT->send();
     exit;
   }
diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc
index 06bef0d..2db3ec3 100644
--- a/program/steps/mail/move_del.inc
+++ b/program/steps/mail/move_del.inc
@@ -39,7 +39,7 @@
         // send error message
 	    if ($_POST['_from'] != 'show')
             $OUTPUT->command('list_mailbox');
-        $OUTPUT->show_message('errormoving', 'error');
+        rcmail_display_server_error('errormoving');
         $OUTPUT->send();
         exit;
     }
@@ -60,7 +60,7 @@
         // send error message
 	    if ($_POST['_from'] != 'show')
             $OUTPUT->command('list_mailbox');
-        $OUTPUT->show_message('errordeleting', 'error');
+        rcmail_display_server_error('errordeleting');
         $OUTPUT->send();
         exit;
     }
diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc
index 90d1c37..39fb32f 100644
--- a/program/steps/mail/search.inc
+++ b/program/steps/mail/search.inc
@@ -124,8 +124,7 @@
 }
 // handle IMAP errors (e.g. #1486905)
 else  if ($err_code = $IMAP->get_error_code()) {
-  $err_str = $IMAP->get_error_str();
-  $OUTPUT->show_message('servererrormsg', 'error', array('msg' => $err_str));
+  rcmail_display_server_error();
 }
 else {
   $OUTPUT->show_message('searchnomatch', 'notice');
diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc
index 1ceca74..7ae4fb3 100644
--- a/program/steps/settings/folders.inc
+++ b/program/steps/settings/folders.inc
@@ -34,7 +34,7 @@
 
         // Handle virtual (non-existing) folders
         if (!$result && $IMAP->get_error_code() == -1 &&
-            strpos($IMAP->get_error_str(), '[TRYCREATE]')
+            $IMAP->get_response_code() == rcube_imap::TRYCREATE
         ) {
             $result = $IMAP->create_mailbox($mbox, true);
             if ($result) {
@@ -45,7 +45,7 @@
         if ($result)
             $OUTPUT->show_message('foldersubscribed', 'confirmation');
         else
-            $OUTPUT->show_message('errorsaving', 'error');
+            rcmail_display_server_error('errorsaving');
     }
 }
 
@@ -58,7 +58,7 @@
         if ($result)
             $OUTPUT->show_message('folderunsubscribed', 'confirmation');
         else
-            $OUTPUT->show_message('errorsaving', 'error');
+            rcmail_display_server_error('errorsaving');
     }
 }
 
@@ -92,7 +92,7 @@
         $OUTPUT->command('set_quota', rcmail_quota_content());
     }
     else if (!$deleted) {
-        $OUTPUT->show_message('errorsaving', 'error');
+        rcmail_display_server_error('errorsaving');
     }
 }
 
@@ -141,7 +141,7 @@
             rcube_charset_convert($name, 'UTF7-IMAP'), $display_rename, $before);
     }
     else if (!$rename) {
-        $OUTPUT->show_message('errorsaving', 'error');
+        rcmail_display_server_error('errorsaving');
     }
 }
 
@@ -179,7 +179,7 @@
         $OUTPUT->command('show_folder', $mbox_utf8, null, true);
     }
     else {
-        $OUTPUT->show_message('errorsaving', 'error');
+        rcmail_display_server_error('errorsaving');
     }
 }
 
@@ -195,6 +195,9 @@
     if ($size !== false) {
         $OUTPUT->command('folder_size_update', show_bytes($size));
     }
+    else {
+        rcmail_display_server_error();
+    }
 }
 
 if ($OUTPUT->ajax_call)

--
Gitblit v1.9.1