From 0c144b98a417d50b9c073ae3931fdad47390f1f7 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sun, 23 Sep 2012 16:44:13 -0400
Subject: [PATCH] Merge branch 'master' of github.com:roundcube/roundcubemail

---
 plugins/http_authentication/http_authentication.php   |   86 +++++---
 tests/Framework/Html.php                              |    5 
 program/include/rcube_utils.php                       |    3 
 config/db.inc.php.dist                                |    1 
 plugins/managesieve/localization/en_US.inc            |    9 +
 plugins/managesieve/managesieve.php                   |   79 ++++++++
 plugins/http_authentication/package.xml               |    6 
 plugins/managesieve/lib/rcube_sieve_script.php        |   96 ++++++++++
 plugins/managesieve/config.inc.php.dist               |    5 
 plugins/managesieve/localization/en_GB.inc            |    9 +
 program/steps/settings/func.inc                       |    2 
 tests/phpunit.xml                                     |    2 
 plugins/managesieve/tests/src/parser_notify_a         |   18 ++
 plugins/managesieve/package.xml                       |    4 
 plugins/managesieve/tests/src/parser_notify_b         |   17 +
 CHANGELOG                                             |    1 
 program/include/html.php                              |   23 -
 plugins/managesieve/localization/pl_PL.inc            |    9 +
 plugins/managesieve/tests/Parser.php                  |   10 +
 program/steps/addressbook/edit.inc                    |    7 
 plugins/managesieve/tests/src/parser_enotify_a        |   19 ++
 plugins/managesieve/tests/src/parser_enotify_b        |   18 ++
 program/steps/addressbook/func.inc                    |    5 
 plugins/managesieve/Changelog                         |    2 
 program/steps/addressbook/import.inc                  |    2 
 program/include/rcube_output_html.php                 |    2 
 program/include/rcmail.php                            |    2 
 plugins/database_attachments/database_attachments.php |   32 +-
 plugins/managesieve/managesieve.js                    |    6 
 program/js/app.js                                     |    6 
 30 files changed, 378 insertions(+), 108 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 8a010a4..61eced8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix HTML special characters handling in message list/header display (#1488523)
 - List related text/html part as attachment in plain text mode (#1488677)
 - Use IMAP BINARY (RFC3516) extension to fetch message/part bodies
 - Fix folder creation under public namespace root (#1488665)
diff --git a/config/db.inc.php.dist b/config/db.inc.php.dist
index a02d7dc..49ec245 100644
--- a/config/db.inc.php.dist
+++ b/config/db.inc.php.dist
@@ -58,7 +58,6 @@
 $rcmail_config['db_sequence_identities'] = 'identity_ids';
 $rcmail_config['db_sequence_contacts'] = 'contact_ids';
 $rcmail_config['db_sequence_contactgroups'] = 'contactgroups_ids';
-$rcmail_config['db_sequence_cache'] = 'cache_ids';
 $rcmail_config['db_sequence_searches'] = 'search_ids';
 
 
diff --git a/plugins/database_attachments/database_attachments.php b/plugins/database_attachments/database_attachments.php
index 9a279f5..079f4e5 100644
--- a/plugins/database_attachments/database_attachments.php
+++ b/plugins/database_attachments/database_attachments.php
@@ -46,9 +46,9 @@
         $data = base64_encode($data);
 
         $status = $rcmail->db->query(
-            "INSERT INTO ".get_table_name('cache')."
-             (created, user_id, cache_key, data)
-             VALUES (".$rcmail->db->now().", ?, ?, ?)",
+            "INSERT INTO ".get_table_name('cache')
+             ." (created, user_id, cache_key, data)"
+             ." VALUES (".$rcmail->db->now().", ?, ?, ?)",
             $_SESSION['user_id'],
             $key,
             $data);
@@ -82,9 +82,9 @@
         $data = base64_encode($args['data']);
 
         $status = $rcmail->db->query(
-            "INSERT INTO ".get_table_name('cache')."
-             (created, user_id, cache_key, data)
-             VALUES (".$rcmail->db->now().", ?, ?, ?)",
+            "INSERT INTO ".get_table_name('cache')
+             ." (created, user_id, cache_key, data)"
+             ." VALUES (".$rcmail->db->now().", ?, ?, ?)",
             $_SESSION['user_id'],
             $key,
             $data);
@@ -106,9 +106,9 @@
         $args['status'] = false;
         $rcmail = rcmail::get_instance();
         $status = $rcmail->db->query(
-            "DELETE FROM ".get_table_name('cache')."
-             WHERE  user_id=?
-             AND    cache_key=?",
+            "DELETE FROM ".get_table_name('cache')
+             ." WHERE user_id = ?"
+                ." AND cache_key = ?",
             $_SESSION['user_id'],
             $args['id']);
 
@@ -138,10 +138,10 @@
         $rcmail = rcmail::get_instance();
 
         $sql_result = $rcmail->db->query(
-            "SELECT cache_id, data
-             FROM ".get_table_name('cache')."
-             WHERE  user_id=?
-             AND    cache_key=?",
+            "SELECT data"
+             ." FROM ".get_table_name('cache')
+             ." WHERE user_id=?"
+                ." AND cache_key=?",
             $_SESSION['user_id'],
             $args['id']);
 
@@ -161,9 +161,9 @@
         $prefix = $this->cache_prefix . $args['group'];
         $rcmail = rcmail::get_instance();
         $rcmail->db->query(
-            "DELETE FROM ".get_table_name('cache')."
-             WHERE  user_id=?
-             AND cache_key like '{$prefix}%'",
+            "DELETE FROM ".get_table_name('cache')
+            ." WHERE user_id = ?"
+                ." AND cache_key LIKE '{$prefix}%'",
             $_SESSION['user_id']);
     }
 }
diff --git a/plugins/http_authentication/http_authentication.php b/plugins/http_authentication/http_authentication.php
index 6c87371..a14b5cb 100644
--- a/plugins/http_authentication/http_authentication.php
+++ b/plugins/http_authentication/http_authentication.php
@@ -17,51 +17,67 @@
  */
 class http_authentication extends rcube_plugin
 {
-  public $task = 'login|logout';
 
-  function init()
-  {
-    $this->add_hook('startup', array($this, 'startup'));
-    $this->add_hook('authenticate', array($this, 'authenticate'));
-    $this->add_hook('logout_after', array($this, 'logout'));
-  }
+    function init()
+    {
+        $this->add_hook('startup', array($this, 'startup'));
+        $this->add_hook('authenticate', array($this, 'authenticate'));
+        $this->add_hook('logout_after', array($this, 'logout'));
+    }
 
-  function startup($args)
-  {
-    // change action to login
-    if (empty($args['action']) && empty($_SESSION['user_id'])
-        && !empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW']))
-      $args['action'] = 'login';
+    function startup($args)
+    {
+        if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
+            $rcmail = rcmail::get_instance();
+            $rcmail->add_shutdown_function(array('http_authentication', 'shutdown'));
 
-    return $args;
-  }
+            // handle login action
+            if (empty($args['action']) && empty($_SESSION['user_id'])) {
+                $args['action'] = 'login';
+            }
+            // Set user password in session (see shutdown() method for more info)
+            else if (!empty($_SESSION['user_id']) && empty($_SESION['password'])) {
+                $_SESSION['password'] = $rcmail->encrypt($_SERVER['PHP_AUTH_PW']);
+            }
+        }
 
-  function authenticate($args)
-  {
-    // Allow entering other user data in login form,
-    // e.g. after log out (#1487953)
-    if (!empty($args['user'])) {
         return $args;
     }
 
-    if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
-      $args['user'] = $_SERVER['PHP_AUTH_USER'];
-      $args['pass'] = $_SERVER['PHP_AUTH_PW'];
+    function authenticate($args)
+    {
+        // Allow entering other user data in login form,
+        // e.g. after log out (#1487953)
+        if (!empty($args['user'])) {
+            return $args;
+        }
+
+        if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
+            $args['user'] = $_SERVER['PHP_AUTH_USER'];
+            $args['pass'] = $_SERVER['PHP_AUTH_PW'];
+        }
+
+        $args['cookiecheck'] = false;
+        $args['valid'] = true;
+
+        return $args;
     }
 
-    $args['cookiecheck'] = false;
-    $args['valid'] = true;
-
-    return $args;
-  }
-  
-  function logout($args)
-  {
-    // redirect to configured URL in order to clear HTTP auth credentials
-    if (!empty($_SERVER['PHP_AUTH_USER']) && $args['user'] == $_SERVER['PHP_AUTH_USER'] && ($url = rcmail::get_instance()->config->get('logout_url'))) {
-      header("Location: $url", true, 307);
+    function logout($args)
+    {
+        // redirect to configured URL in order to clear HTTP auth credentials
+        if (!empty($_SERVER['PHP_AUTH_USER']) && $args['user'] == $_SERVER['PHP_AUTH_USER']) {
+            if ($url = rcmail::get_instance()->config->get('logout_url')) {
+                header("Location: $url", true, 307);
+            }
+        }
     }
-  }
 
+    function shutdown()
+    {
+        // There's no need to store password (even if encrypted) in session
+        // We'll set it back on startup (#1486553)
+        rcmail::get_instance()->session->remove('password');
+    }
 }
 
diff --git a/plugins/http_authentication/package.xml b/plugins/http_authentication/package.xml
index d8f2036..aab3819 100644
--- a/plugins/http_authentication/package.xml
+++ b/plugins/http_authentication/package.xml
@@ -13,10 +13,10 @@
 		<email>roundcube@gmail.com</email>
 		<active>yes</active>
 	</lead>
-	<date>2011-11-21</date>
+	<date>2012-09-18</date>
 	<version>
-		<release>1.4</release>
-		<api>1.4</api>
+		<release>1.5</release>
+		<api>1.5</api>
 	</version>
 	<stability>
 		<release>stable</release>
diff --git a/plugins/managesieve/Changelog b/plugins/managesieve/Changelog
index 482cff0..a1dd7e0 100644
--- a/plugins/managesieve/Changelog
+++ b/plugins/managesieve/Changelog
@@ -1,4 +1,6 @@
 - Fixed issue with DBMail bug [http://pear.php.net/bugs/bug.php?id=19077] (#1488594)
+- Added support for enotify/notify (RFC5435, RFC5436, draft-ietf-sieve-notify-00)
+- Change default port to 4190 (IANA-allocated), add port auto-detection (#1488713)
 
 * version 5.2 [2012-07-24]
 -----------------------------------------------------------
diff --git a/plugins/managesieve/config.inc.php.dist b/plugins/managesieve/config.inc.php.dist
index cb9b2a9..1f34564 100644
--- a/plugins/managesieve/config.inc.php.dist
+++ b/plugins/managesieve/config.inc.php.dist
@@ -1,7 +1,8 @@
 <?php
 
-// managesieve server port
-$rcmail_config['managesieve_port'] = 2000;
+// managesieve server port. When empty the port will be determined automatically
+// using getservbyname() function, with 4190 as a fallback.
+$rcmail_config['managesieve_port'] = null;
 
 // managesieve server address, default is localhost.
 // Replacement variables supported in host name:
diff --git a/plugins/managesieve/lib/rcube_sieve_script.php b/plugins/managesieve/lib/rcube_sieve_script.php
index aa3b9ab..36eb1bc 100644
--- a/plugins/managesieve/lib/rcube_sieve_script.php
+++ b/plugins/managesieve/lib/rcube_sieve_script.php
@@ -41,7 +41,9 @@
         'variables',                // RFC5229
         'body',                     // RFC5173
         'subaddress',               // RFC5233
-        // @TODO: enotify/notify, spamtest+virustest, mailbox, date
+        'enotify',                  // RFC5435
+        'notify',                   // draft-ietf-sieve-notify-00
+        // @TODO: spamtest+virustest, mailbox, date
     );
 
     /**
@@ -197,6 +199,9 @@
                 }
             }
         }
+
+        $imapflags = in_array('imap4flags', $this->supported) ? 'imap4flags' : 'imapflags';
+        $notify    = in_array('enotify', $this->supported) ? 'enotify' : 'notify';
 
         // rules
         foreach ($this->content as $rule) {
@@ -370,11 +375,7 @@
                     case 'addflag':
                     case 'setflag':
                     case 'removeflag':
-                        if (in_array('imap4flags', $this->supported))
-                            array_push($exts, 'imap4flags');
-                        else
-                            array_push($exts, 'imapflags');
-
+                        array_push($exts, $imapflags);
                         $action_script .= $action['type'].' '
                             . self::escape_string($action['target']);
                         break;
@@ -401,6 +402,46 @@
                             $action_script .= ":$opt ";
                         }
                         $action_script .= self::escape_string($action['name']) . ' ' . self::escape_string($action['value']);
+                        break;
+
+                    case 'notify':
+                        array_push($exts, $notify);
+                        $action_script .= 'notify';
+
+                        // Here we support only 00 version of notify draft, there
+                        // were a couple regressions in 00 to 04 changelog, we use
+                        // the version used by Cyrus
+                        if ($notify == 'notify') {
+                            switch ($action['importance']) {
+                                case 1: $action_script .= " :high"; break;
+                                case 2: $action_script .= " :normal"; break;
+                                case 3: $action_script .= " :low"; break;
+
+                            }
+                            unset($action['importance']);
+                        }
+
+                        foreach (array('from', 'importance', 'options', 'message') as $n_tag) {
+                            if (!empty($action[$n_tag])) {
+                                $action_script .= " :$n_tag " . self::escape_string($action[$n_tag]);
+                            }
+                        }
+
+                        if (!empty($action['address'])) {
+                            $method = 'mailto:' . $action['address'];
+                            if (!empty($action['body'])) {
+                                $method .= '?body=' . rawurlencode($action['body']);
+                            }
+                        }
+                        else {
+                            $method = $action['method'];
+                        }
+
+                        // method is optional in notify extension
+                        if (!empty($method)) {
+                            $action_script .= ($notify == 'notify' ? " :method " : " ") . self::escape_string($method);
+                        }
+
                         break;
 
                     case 'vacation':
@@ -840,6 +881,49 @@
                 // $result[] = array('type' => 'require', 'target' => $tokens);
                 break;
 
+            case 'notify':
+                $notify = array('type' => 'notify');
+                $priorities = array(':high' => 1, ':normal' => 2, ':low' => 3);
+
+                // Parameters: :from, :importance, :options, :message
+                //     additional (optional) :method parameter for notify extension
+                for ($i=0, $len=count($tokens); $i<$len; $i++) {
+                    $tok = strtolower($tokens[$i]);
+                    if ($tok[0] == ':') {
+                        // Here we support only 00 version of notify draft, there
+                        // were a couple regressions in 00 to 04 changelog, we use
+                        // the version used by Cyrus
+                        if (isset($priorities[$tok])) {
+                            $notify['importance'] = $priorities[$tok];
+                        }
+                        else {
+                            $notify[substr($tok, 1)] = $tokens[++$i];
+                        }
+                    }
+                    else {
+                        // unnamed parameter is a :method in enotify extension
+                        $notify['method'] = $tokens[$i];
+                    }
+                }
+
+                $method_components = parse_url($notify['method']);
+                if ($method_components['scheme'] == 'mailto') {
+                    $notify['address'] = $method_components['path'];
+                    $method_params = array();
+                    if (array_key_exists('query', $method_components)) {
+                        parse_str($method_components['query'], $method_params);
+                    }
+                    $method_params = array_change_key_case($method_params, CASE_LOWER);
+                    // magic_quotes_gpc and magic_quotes_sybase affect the output of parse_str
+                    if (ini_get('magic_quotes_gpc') || ini_get('magic_quotes_sybase')) {
+                        array_map('stripslashes', $method_params);
+                    }
+                    $notify['body'] = (array_key_exists('body', $method_params)) ? $method_params['body'] : '';
+                }
+
+                $result[] = $notify;
+                break;
+
             }
 
             if ($separator == $end)
diff --git a/plugins/managesieve/localization/en_GB.inc b/plugins/managesieve/localization/en_GB.inc
index 6859f5f..f9075b8 100644
--- a/plugins/managesieve/localization/en_GB.inc
+++ b/plugins/managesieve/localization/en_GB.inc
@@ -97,6 +97,15 @@
 $labels['setvarname'] = 'Variable name:';
 $labels['setvarvalue'] = 'Variable value:';
 $labels['setvarmodifiers'] = 'Modifiers:';
+$labels['notify'] = 'Send notification';
+$labels['notifyaddress'] = 'To e-mail address:';
+$labels['notifybody'] = 'Notification body:';
+$labels['notifysubject'] = 'Notification subject:';
+$labels['notifyfrom'] = 'Notification sender:';
+$labels['notifyimportance'] = 'Importance:';
+$labels['notifyimportancelow'] = 'low';
+$labels['notifyimportancenormal'] = 'normal';
+$labels['notifyimportancehigh'] = 'high';
 $labels['filtercreate'] = 'Create filter';
 $labels['usedata'] = 'Use following data in the filter:';
 $labels['nextstep'] = 'Next Step';
diff --git a/plugins/managesieve/localization/en_US.inc b/plugins/managesieve/localization/en_US.inc
index 1912913..cb223c1 100644
--- a/plugins/managesieve/localization/en_US.inc
+++ b/plugins/managesieve/localization/en_US.inc
@@ -88,6 +88,15 @@
 $labels['varupperfirst'] = 'first character upper-case';
 $labels['varquotewildcard'] = 'quote special characters';
 $labels['varlength'] = 'length';
+$labels['notify'] = 'Send notification';
+$labels['notifyaddress'] = 'To e-mail address:';
+$labels['notifybody'] = 'Notification body:';
+$labels['notifysubject'] = 'Notification subject:';
+$labels['notifyfrom'] = 'Notification sender:';
+$labels['notifyimportance'] = 'Importance:';
+$labels['notifyimportancelow'] = 'low';
+$labels['notifyimportancenormal'] = 'normal';
+$labels['notifyimportancehigh'] = 'high';
 $labels['filtercreate'] = 'Create filter';
 $labels['usedata'] = 'Use following data in the filter:';
 $labels['nextstep'] = 'Next Step';
diff --git a/plugins/managesieve/localization/pl_PL.inc b/plugins/managesieve/localization/pl_PL.inc
index 0b4cb60..734a4eb 100644
--- a/plugins/managesieve/localization/pl_PL.inc
+++ b/plugins/managesieve/localization/pl_PL.inc
@@ -103,6 +103,15 @@
 $labels['varupperfirst'] = 'pierwsza litera duża (:upperfirst)';
 $labels['varquotewildcard'] = 'anulowane znaki specjalne (:quotewildcard)';
 $labels['varlength'] = 'długość (:length)';
+$labels['notify'] = 'Wyślij powiadomienie';
+$labels['notifyaddress'] = 'Na adres e-mail:';
+$labels['notifybody'] = 'Treść powiadomienia:';
+$labels['notifysubject'] = 'Temat powiadomienia:';
+$labels['notifyfrom'] = 'Nadawca powiadomienia:';
+$labels['notifyimportance'] = 'Priorytet:';
+$labels['notifyimportancelow'] = 'niski';
+$labels['notifyimportancenormal'] = 'normalny';
+$labels['notifyimportancehigh'] = 'wysoki';
 $labels['filtercreate'] = 'Utwórz filtr';
 $labels['usedata'] = 'Użyj następujących danych do utworzenia filtra:';
 $labels['nextstep'] = 'Następny krok';
diff --git a/plugins/managesieve/managesieve.js b/plugins/managesieve/managesieve.js
index f447719..bbc1079 100644
--- a/plugins/managesieve/managesieve.js
+++ b/plugins/managesieve/managesieve.js
@@ -639,7 +639,8 @@
       target_area: document.getElementById('action_target_area' + id),
       flags: document.getElementById('action_flags' + id),
       vacation: document.getElementById('action_vacation' + id),
-      set: document.getElementById('action_set' + id)
+      set: document.getElementById('action_set' + id),
+      notify: document.getElementById('action_notify' + id)
     };
 
   if (obj.value == 'fileinto' || obj.value == 'fileinto_copy') {
@@ -660,6 +661,9 @@
   else if (obj.value == 'set') {
     enabled.set = 1;
   }
+  else if (obj.value == 'notify') {
+    enabled.notify = 1;
+  }
 
   for (var x in elems) {
     elems[x].style.display = !enabled[x] ? 'none' : 'inline';
diff --git a/plugins/managesieve/managesieve.php b/plugins/managesieve/managesieve.php
index e7828f1..0ddeba5 100644
--- a/plugins/managesieve/managesieve.php
+++ b/plugins/managesieve/managesieve.php
@@ -7,13 +7,13 @@
  * It's clickable interface which operates on text scripts and communicates
  * with server using managesieve protocol. Adds Filters tab in Settings.
  *
- * @version 5.0
+ * @version @package_version@
  * @author Aleksander Machniak <alec@alec.pl>
  *
  * Configuration (see config.inc.php.dist)
  *
- * Copyright (C) 2008-2011, The Roundcube Dev Team
- * Copyright (C) 2011, Kolab Systems AG
+ * Copyright (C) 2008-2012, The Roundcube Dev Team
+ * Copyright (C) 2011-2012, Kolab Systems AG
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2
@@ -62,8 +62,9 @@
         "x-beenthere",
     );
 
-    const VERSION = '5.2';
+    const VERSION  = '5.2';
     const PROGNAME = 'Roundcube (Managesieve)';
+    const PORT     = 4190;
 
 
     function init()
@@ -200,9 +201,15 @@
         set_include_path($include_path);
 
         $host = rcube_parse_host($this->rc->config->get('managesieve_host', 'localhost'));
-        $port = $this->rc->config->get('managesieve_port', 2000);
-
         $host = rcube_idn_to_ascii($host);
+
+        $port = $this->rc->config->get('managesieve_port');
+        if (empty($port)) {
+            $port = getservbyname('sieve', 'tcp');
+            if (empty($port)) {
+                $port = self::PORT;
+            }
+        }
 
         $plugin = $this->rc->plugins->exec_hook('managesieve_connect', array(
             'user'      => $_SESSION['username'],
@@ -625,6 +632,11 @@
             $varnames       = get_input_value('_action_varname', RCUBE_INPUT_POST);
             $varvalues      = get_input_value('_action_varvalue', RCUBE_INPUT_POST);
             $varmods        = get_input_value('_action_varmods', RCUBE_INPUT_POST);
+            $notifyaddrs    = get_input_value('_action_notifyaddress', RCUBE_INPUT_POST);
+            $notifybodies   = get_input_value('_action_notifybody', RCUBE_INPUT_POST);
+            $notifymessages = get_input_value('_action_notifymessage', RCUBE_INPUT_POST);
+            $notifyfrom     = get_input_value('_action_notifyfrom', RCUBE_INPUT_POST);
+            $notifyimp      = get_input_value('_action_notifyimportance', RCUBE_INPUT_POST);
 
             // we need a "hack" for radiobuttons
             foreach ($sizeitems as $item)
@@ -877,6 +889,23 @@
                     if (!isset($varvalues[$idx]) || $varvalues[$idx] === '') {
                         $this->errors['actions'][$i]['value'] = $this->gettext('cannotbeempty');
                     }
+                    break;
+
+                case 'notify':
+                    if (empty($notifyaddrs[$idx])) {
+                        $this->errors['actions'][$i]['address'] = $this->gettext('cannotbeempty');
+                    }
+                    else if (!check_email($notifyaddrs[$idx])) {
+                        $this->errors['actions'][$i]['address'] = $this->gettext('noemailwarning');
+                    }
+                    if (!empty($notifyfrom[$idx]) && !check_email($notifyfrom[$idx])) {
+                        $this->errors['actions'][$i]['from'] = $this->gettext('noemailwarning');
+                    }
+                    $this->form['actions'][$i]['address'] = $notifyaddrs[$idx];
+                    $this->form['actions'][$i]['body'] = $notifybodies[$idx];
+                    $this->form['actions'][$i]['message'] = $notifymessages[$idx];
+                    $this->form['actions'][$i]['from'] = $notifyfrom[$idx];
+                    $this->form['actions'][$i]['importance'] = $notifyimp[$idx];
                     break;
                 }
 
@@ -1479,6 +1508,9 @@
         if (in_array('variables', $this->exts)) {
             $select_action->add(Q($this->gettext('setvariable')), 'set');
         }
+        if (in_array('enotify', $this->exts) || in_array('notify', $this->exts)) {
+            $select_action->add(Q($this->gettext('notify')), 'notify');
+        }
         $select_action->add(Q($this->gettext('rulestop')), 'stop');
 
         $select_type = $action['type'];
@@ -1571,6 +1603,41 @@
         }
         $out .= '</div>';
 
+        // notify
+        // skip :options tag - not used by the mailto method
+        $out .= '<div id="action_notify' .$id.'" style="display:' .($action['type']=='notify' ? 'inline' : 'none') .'">';
+        $out .= '<span class="label">' .Q($this->gettext('notifyaddress')) . '</span><br />'
+            .'<input type="text" name="_action_notifyaddress['.$id.']" id="action_notifyaddress'.$id.'" '
+            .'value="' . Q($action['address']) . '" size="35" '
+            . $this->error_class($id, 'action', 'address', 'action_notifyaddress') .' />';
+        $out .= '<br /><span class="label">'. Q($this->gettext('notifybody')) .'</span><br />'
+            .'<textarea name="_action_notifybody['.$id.']" id="action_notifybody' .$id. '" '
+            .'rows="3" cols="35" '. $this->error_class($id, 'action', 'method', 'action_notifybody') . '>'
+            . Q($action['body'], 'strict', false) . "</textarea>\n";
+        $out .= '<br /><span class="label">' .Q($this->gettext('notifysubject')) . '</span><br />'
+            .'<input type="text" name="_action_notifymessage['.$id.']" id="action_notifymessage'.$id.'" '
+            .'value="' . Q($action['message']) . '" size="35" '
+            . $this->error_class($id, 'action', 'message', 'action_notifymessage') .' />';
+        $out .= '<br /><span class="label">' .Q($this->gettext('notifyfrom')) . '</span><br />'
+            .'<input type="text" name="_action_notifyfrom['.$id.']" id="action_notifyfrom'.$id.'" '
+            .'value="' . Q($action['from']) . '" size="35" '
+            . $this->error_class($id, 'action', 'from', 'action_notifyfrom') .' />';
+        $importance_options = array(
+            3 => 'notifyimportancelow',
+            2 => 'notifyimportancenormal',
+            1 => 'notifyimportancehigh'
+        );
+        $select_importance = new html_select(array(
+            'name' => '_action_notifyimportance[' . $id . ']',
+            'id' => '_action_notifyimportance' . $id,
+            'class' => $this->error_class($id, 'action', 'importance', 'action_notifyimportance')));
+        foreach ($importance_options as $io_v => $io_n) {
+            $select_importance->add(Q($this->gettext($io_n)), $io_v);
+        }
+        $out .= '<br /><span class="label">' . Q($this->gettext('notifyimportance')) . '</span><br />';
+        $out .= $select_importance->show($action['importance'] ? $action['importance'] : 2);
+        $out .= '</div>';
+
         // mailbox select
         if ($action['type'] == 'fileinto')
             $mailbox = $this->mod_mailbox($action['target'], 'out');
diff --git a/plugins/managesieve/package.xml b/plugins/managesieve/package.xml
index cde78c9..20fec78 100644
--- a/plugins/managesieve/package.xml
+++ b/plugins/managesieve/package.xml
@@ -17,9 +17,9 @@
 		<email>alec@alec.pl</email>
 		<active>yes</active>
 	</lead>
-	<date>2012-06-21</date>
+	<date>2012-07-24</date>
 	<version>
-		<release>5.1</release>
+		<release>5.2</release>
 		<api>5.0</api>
 	</version>
 	<stability>
diff --git a/plugins/managesieve/tests/Parser.php b/plugins/managesieve/tests/Parser.php
index 00915cc..2f24870 100644
--- a/plugins/managesieve/tests/Parser.php
+++ b/plugins/managesieve/tests/Parser.php
@@ -15,7 +15,15 @@
      */
     function test_parser($input, $output, $message)
     {
-        $script = new rcube_sieve_script($input);
+        // get capabilities list from the script
+        $caps = array();
+        if (preg_match('/require \[([a-z0-9", ]+)\]/', $input, $m)) {
+            foreach (explode(',', $m[1]) as $cap) {
+                $caps[] = trim($cap, '" ');
+            }
+        }
+
+        $script = new rcube_sieve_script($input, $caps);
         $result = $script->as_text();
 
         $this->assertEquals(trim($result), trim($output), $message);
diff --git a/plugins/managesieve/tests/src/parser_enotify_a b/plugins/managesieve/tests/src/parser_enotify_a
new file mode 100644
index 0000000..68a9ef5
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_enotify_a
@@ -0,0 +1,19 @@
+require ["enotify","variables"];
+# rule:[notify1]
+if header :contains "from" "boss@example.org"
+{
+	notify :importance "1" :message "This is probably very important" "mailto:alm@example.com";
+	stop;
+}
+# rule:[subject]
+if header :matches "Subject" "*"
+{
+	set "subject" "${1}";
+}
+# rule:[from notify2]
+if header :matches "From" "*"
+{
+	set "from" "${1}";
+	notify :importance "3" :message "${from}: ${subject}" "mailto:alm@example.com";
+}
+
diff --git a/plugins/managesieve/tests/src/parser_enotify_b b/plugins/managesieve/tests/src/parser_enotify_b
new file mode 100644
index 0000000..8854658
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_enotify_b
@@ -0,0 +1,18 @@
+require ["envelope","variables","enotify"];
+# rule:[from]
+if envelope :all :matches "from" "*"
+{
+	set "env_from" " [really: ${1}]";
+}
+# rule:[subject]
+if header :matches "Subject" "*"
+{
+	set "subject" "${1}";
+}
+# rule:[from notify]
+if address :all :matches "from" "*"
+{
+	set "from_addr" "${1}";
+	notify :message "${from_addr}${env_from}: ${subject}" "mailto:alm@example.com";
+}
+
diff --git a/plugins/managesieve/tests/src/parser_notify_a b/plugins/managesieve/tests/src/parser_notify_a
new file mode 100644
index 0000000..f1a5754
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_notify_a
@@ -0,0 +1,18 @@
+require ["notify","variables"];
+# rule:[notify1]
+if header :contains "from" "boss@example.org"
+{
+	notify :low :message "This is probably very important";
+	stop;
+}
+# rule:[subject]
+if header :matches "Subject" "*"
+{
+	set "subject" "${1}";
+}
+# rule:[from notify2]
+if header :matches "From" "*"
+{
+	set "from" "${1}";
+	notify :high :message "${from}: ${subject}" :method "mailto:test@example.org";
+}
diff --git a/plugins/managesieve/tests/src/parser_notify_b b/plugins/managesieve/tests/src/parser_notify_b
new file mode 100644
index 0000000..cf80a97
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_notify_b
@@ -0,0 +1,17 @@
+require ["envelope","variables","notify"];
+# rule:[from]
+if envelope :all :matches "from" "*"
+{
+	set "env_from" " [really: ${1}]";
+}
+# rule:[subject]
+if header :matches "Subject" "*"
+{
+	set "subject" "${1}";
+}
+# rule:[from notify]
+if address :all :matches "from" "*"
+{
+	set "from_addr" "${1}";
+	notify :message "${from_addr}${env_from}: ${subject}" :method "sms:1234567890";
+}
diff --git a/program/include/html.php b/program/include/html.php
index c6507f8..9487942 100644
--- a/program/include/html.php
+++ b/program/include/html.php
@@ -295,7 +295,7 @@
                 }
             }
             else {
-                $attrib_arr[] = $key . '="' . self::quote($value, true) . '"';
+                $attrib_arr[] = $key . '="' . self::quote($value) . '"';
             }
         }
 
@@ -328,22 +328,13 @@
     /**
      * Replacing specials characters in html attribute value
      *
-     * @param  string  $str       Input string
-     * @param  bool    $validate  Enables double quotation prevention
+     * @param string $str Input string
      *
-     * @return string  The quoted string
+     * @return string The quoted string
      */
-    public static function quote($str, $validate = false)
+    public static function quote($str)
     {
-        $str = htmlspecialchars($str, ENT_COMPAT, RCMAIL_CHARSET);
-
-        // avoid douple quotation of &
-        // @TODO: get rid of it
-        if ($validate) {
-            $str = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $str);
-        }
-
-        return $str;
+        return htmlspecialchars($str, ENT_COMPAT, RCMAIL_CHARSET);
     }
 }
 
@@ -559,7 +550,7 @@
         }
 
         if (!empty($value) && empty($this->attrib['is_escaped'])) {
-            $value = self::quote($value, true);
+            $value = self::quote($value);
         }
 
         return self::tag($this->tagname, $this->attrib, $value,
@@ -635,7 +626,7 @@
 
             $option_content = $option['text'];
             if (empty($this->attrib['is_escaped'])) {
-                $option_content = self::quote($option_content, true);
+                $option_content = self::quote($option_content);
             }
 
             $this->content .= self::tag('option', $attr, $option_content);
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 5a9a1fa..ee144fa 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -281,7 +281,7 @@
         }
         $list[$id] = array(
           'id'       => $id,
-          'name'     => $prop['name'],
+          'name'     => html::quote($prop['name']),
           'groups'   => is_array($prop['groups']),
           'readonly' => !$prop['writable'],
           'hidden'   => $prop['hidden'],
diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php
index 2743e77..6138e2a 100644
--- a/program/include/rcube_output_html.php
+++ b/program/include/rcube_output_html.php
@@ -527,7 +527,7 @@
     {
         $GLOBALS['__version']   = html::quote(RCMAIL_VERSION);
         $GLOBALS['__comm_path'] = html::quote($this->app->comm_path);
-        $GLOBALS['__skin_path'] = Q($this->config->get('skin_path'));
+        $GLOBALS['__skin_path'] = html::quote($this->config->get('skin_path'));
 
         return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
             array($this, 'globals_callback'), $input);
diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php
index c8457b7..2a4d4c4 100644
--- a/program/include/rcube_utils.php
+++ b/program/include/rcube_utils.php
@@ -250,9 +250,6 @@
 
             $out = strtr($str, $encode_arr);
 
-            // avoid douple quotation of &
-            $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
-
             return $newlines ? nl2br($out) : $out;
         }
 
diff --git a/program/js/app.js b/program/js/app.js
index 2182a2b..cf942e2 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -208,7 +208,7 @@
           this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
 
           this.message_list.init();
-          this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', true);
+          this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', 'sort', true);
 
           // load messages
           this.command('list');
@@ -6114,7 +6114,7 @@
               this.show_contentframe(false);
             // disable commands useless when mailbox is empty
             this.enable_command(this.env.message_commands, 'purge', 'expunge',
-              'select-all', 'select-none', 'sort', 'expand-all', 'expand-unread', 'collapse-all', false);
+              'select-all', 'select-none', 'expand-all', 'expand-unread', 'collapse-all', false);
           }
           if (this.message_list)
             this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
@@ -6127,7 +6127,7 @@
         this.env.qsearch = null;
       case 'list':
         if (this.task == 'mail') {
-          this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
+          this.enable_command('show', 'expunge', 'select-all', 'select-none', (this.env.messagecount > 0));
           this.enable_command('purge', this.purge_mailbox_test());
           this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount);
 
diff --git a/program/steps/addressbook/edit.inc b/program/steps/addressbook/edit.inc
index 90069a7..b216a7c 100644
--- a/program/steps/addressbook/edit.inc
+++ b/program/steps/addressbook/edit.inc
@@ -244,11 +244,12 @@
     if (count($sources_list) < 2) {
         $source = $sources_list[$SOURCE_ID];
         $hiddenfield = new html_hiddenfield(array('name' => '_source', 'value' => $SOURCE_ID));
-        return html::span($attrib, Q($source['name']) . $hiddenfield->show());
+        return html::span($attrib, $source['name'] . $hiddenfield->show());
     }
 
-    $attrib['name'] = '_source';
-    $attrib['onchange'] = JS_OBJECT_NAME . ".command('save', 'reload', this.form)";
+    $attrib['name']       = '_source';
+    $attrib['is_escaped'] = true;
+    $attrib['onchange']   = JS_OBJECT_NAME . ".command('save', 'reload', this.form)";
 
     $select = new html_select($attrib);
 
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index 5f5fcc6..4ef4d1b 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -178,7 +178,7 @@
         if (!$name && $source == 0) {
             $name = rcube_label('personaladrbook');
         }
-        $OUTPUT->set_env('sourcename', $name);
+        $OUTPUT->set_env('sourcename', html_entity_decode($name, ENT_COMPAT, 'UTF-8'));
     }
 }
 
@@ -219,12 +219,13 @@
         if ($source['class_name'])
             $class_name .= ' ' . $source['class_name'];
 
+        $name = !empty($source['name']) ? $source['name'] : $id;
         $out .= sprintf($line_templ,
             html_identifier($id),
             $class_name,
             Q(rcmail_url(null, array('_source' => $id))),
             $source['id'],
-            $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id)));
+            $js_id, $name);
 
         $groupdata = array('out' => $out, 'jsdata' => $jsdata, 'source' => $id);
         if ($source['groups'])
diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc
index 15e04b8..fb2251f 100644
--- a/program/steps/addressbook/import.inc
+++ b/program/steps/addressbook/import.inc
@@ -43,7 +43,7 @@
 
   // addressbook selector
   if (count($writable_books) > 1) {
-    $select = new html_select(array('name' => '_target', 'id' => 'rcmimporttarget'));
+    $select = new html_select(array('name' => '_target', 'id' => 'rcmimporttarget', 'is_escaped' => true));
 
     foreach ($writable_books as $book)
         $select->add($book['name'], $book['id']);
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index 59b4e37..4f8da13 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -667,7 +667,7 @@
       $select_abook = new html_select(array('name' => '_default_addressbook', 'id' => $field_id));
 
       foreach ($books as $book) {
-        $select_abook->add($book['name'], $book['id']);
+        $select_abook->add(html_entity_decode($book['name'], ENT_COMPAT, 'UTF-8'), $book['id']);
       }
 
       $blocks['main']['options']['default_addressbook'] = array(
diff --git a/tests/Framework/Html.php b/tests/Framework/Html.php
index 8a27bac..60284de 100644
--- a/tests/Framework/Html.php
+++ b/tests/Framework/Html.php
@@ -31,7 +31,6 @@
             array('>', '&gt;'),
             array('&', '&amp;'),
             array('&amp;', '&amp;amp;'),
-            array('&amp;', '&amp;', true),
         );
     }
 
@@ -39,8 +38,8 @@
      * Test for quote()
      * @dataProvider data_quote
      */
-    function test_quote($str, $result, $validate = false)
+    function test_quote($str, $result)
     {
-        $this->assertEquals(html::quote($str, $validate), $result);
+        $this->assertEquals(html::quote($str), $result);
     }
 }
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 8b38832..43c3b76 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -29,7 +29,7 @@
             <file>HtmlToText.php</file>
             <file>MailFunc.php</file>
         </testsuite>
-        <testsuite name="managesieve">
+        <testsuite name="Managesieve Tests">
             <file>./../plugins/managesieve/tests/Parser.php</file>
             <file>./../plugins/managesieve/tests/Tokenizer.php</file>
         </testsuite>

--
Gitblit v1.9.1