From 700dc66c679f0ae6e16c9d016a15bee4269371e6 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 27 Sep 2012 02:27:02 -0400
Subject: [PATCH] Merge branch 'master' of github.com:roundcube/roundcubemail

---
 plugins/zipdownload/README                        |   35 +
 INSTALL                                           |    2 
 plugins/zipdownload/localization/es_ES.inc        |   10 
 tests/Framework/Html.php                          |    5 
 plugins/zipdownload/localization/it_IT.inc        |   10 
 plugins/managesieve/managesieve.php               |  111 +++++
 plugins/zipdownload/CHANGELOG                     |   34 +
 plugins/managesieve/config.inc.php.dist           |    5 
 program/steps/settings/func.inc                   |    2 
 plugins/zipdownload/localization/gl_ES.inc        |   10 
 plugins/managesieve/tests/src/parser_notify_a     |   18 +
 plugins/managesieve/tests/src/parser_notify_b     |   17 
 CHANGELOG                                         |    2 
 program/include/html.php                          |   23 
 plugins/managesieve/localization/pl_PL.inc        |    9 
 plugins/zipdownload/skins/classic/zip.png         |    0 
 plugins/zipdownload/localization/cs_CZ.inc        |   10 
 plugins/zipdownload/localization/pt_BR.inc        |    9 
 program/steps/addressbook/import.inc              |    2 
 program/include/rcube_output_html.php             |    2 
 plugins/zipdownload/localization/tr_TR.inc        |   10 
 plugins/zipdownload/localization/es_AR.inc        |    9 
 plugins/zipdownload/localization/en_GB.inc        |   10 
 .gitignore                                        |    1 
 program/include/rcube_utils.php                   |    5 
 plugins/zipdownload/localization/nl_NL.inc        |   10 
 plugins/managesieve/localization/en_US.inc        |    9 
 plugins/zipdownload/package.xml                   |   89 ++++
 plugins/zipdownload/config.inc.php.dist           |   21 +
 plugins/zipdownload/localization/de_DE.inc        |   10 
 plugins/managesieve/lib/rcube_sieve_script.php    |   96 +++++
 plugins/managesieve/localization/en_GB.inc        |    9 
 plugins/zipdownload/localization/en_US.inc        |   10 
 plugins/zipdownload/localization/ro_RO.inc        |    9 
 plugins/zipdownload/zipdownload.php               |  267 ++++++++++++++
 tests/phpunit.xml                                 |    2 
 plugins/zipdownload/localization/fr_FR.inc        |   10 
 plugins/managesieve/package.xml                   |    4 
 plugins/zipdownload/skins/classic/zipdownload.css |    8 
 program/lib/html2text.php                         |    2 
 program/steps/addressbook/edit.inc                |    7 
 plugins/managesieve/tests/src/parser_enotify_a    |   19 +
 plugins/zipdownload/localization/de_CH.inc        |   10 
 plugins/managesieve/tests/src/parser_enotify_b    |   18 +
 program/steps/addressbook/func.inc                |    5 
 plugins/managesieve/Changelog                     |    3 
 plugins/zipdownload/skins/larry/zipdownload.css   |    7 
 plugins/zipdownload/localization/ca_ES.inc        |   10 
 plugins/zipdownload/zipdownload.js                |   33 +
 plugins/zipdownload/localization/da_DK.inc        |   10 
 plugins/zipdownload/localization/et_EE.inc        |    9 
 plugins/zipdownload/localization/hu_HU.inc        |   10 
 plugins/zipdownload/localization/pl_PL.inc        |   10 
 program/include/rcmail.php                        |    2 
 plugins/zipdownload/localization/ru_RU.inc        |   10 
 plugins/managesieve/managesieve.js                |    6 
 56 files changed, 1,023 insertions(+), 53 deletions(-)

diff --git a/.gitignore b/.gitignore
index 589c347..f55131f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 logs/*
 temp/*
 config/*
+plugins/*/config.inc.php
\ No newline at end of file
diff --git a/CHANGELOG b/CHANGELOG
index 8a010a4..598eff9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,8 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Integrated zipdownload plugin to download all attachments (#1445509)
+- 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/INSTALL b/INSTALL
index f6d305a..3021768 100644
--- a/INSTALL
+++ b/INSTALL
@@ -13,7 +13,7 @@
 * .htaccess support allowing overrides for DirectoryIndex
 * PHP Version 5.2.1 or greater including
    - PDO, PCRE, DOM, JSON, XML, Session, Sockets (required)
-   - libiconv (recommended)
+   - libiconv, zip (recommended)
    - mbstring, fileinfo, mcrypt (optional)
 * PEAR packages distributed with Roundcube or external:
    - Mail_Mime 1.8.1 or newer
diff --git a/plugins/managesieve/Changelog b/plugins/managesieve/Changelog
index 482cff0..c0428c4 100644
--- a/plugins/managesieve/Changelog
+++ b/plugins/managesieve/Changelog
@@ -1,4 +1,7 @@
 - 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)
+- Added request size limits detection and script corruption prevention (#1488648)
 
 * 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..7282ff2 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'],
@@ -523,9 +530,37 @@
         // Init plugin and handle managesieve connection
         $error = $this->managesieve_start();
 
-        // filters set add action
-        if (!empty($_POST['_newset'])) {
+        // get request size limits (#1488648)
+        $max_post = max(array(
+            ini_get('max_input_vars'),
+            ini_get('suhosin.request.max_vars'),
+            ini_get('suhosin.post.max_vars'),
+        ));
+        $max_depth = max(array(
+            ini_get('suhosin.request.max_array_depth'),
+            ini_get('suhosin.post.max_array_depth'),
+        ));
 
+        // check request size limit
+        if ($max_post && count($_POST, COUNT_RECURSIVE) >= $max_post) {
+            rcube::raise_error(array(
+                'code' => 500, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Request size limit exceeded (one of max_input_vars/suhosin.request.max_vars/suhosin.post.max_vars)"
+                ), true, false);
+            $this->rc->output->show_message('managesieve.filtersaveerror', 'error');
+        }
+        // check request depth limits
+        else if ($max_depth && count($_POST['_header']) > $max_depth) {
+            rcube::raise_error(array(
+                'code' => 500, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Request size limit exceeded (one of suhosin.request.max_array_depth/suhosin.post.max_array_depth)"
+                ), true, false);
+            $this->rc->output->show_message('managesieve.filtersaveerror', 'error');
+        }
+        // filters set add action
+        else if (!empty($_POST['_newset'])) {
             $name       = get_input_value('_name', RCUBE_INPUT_POST, true);
             $copy       = get_input_value('_copy', RCUBE_INPUT_POST, true);
             $from       = get_input_value('_from', RCUBE_INPUT_POST);
@@ -625,6 +660,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 +917,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 +1536,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 +1631,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/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/plugins/zipdownload/CHANGELOG b/plugins/zipdownload/CHANGELOG
new file mode 100644
index 0000000..32b878e
--- /dev/null
+++ b/plugins/zipdownload/CHANGELOG
@@ -0,0 +1,34 @@
+Roundcube Webmail ZipDownload
+=============================
+
+2012-09-20
+==========
+ * Added style for new Larry skin
+ * Made plugin work with 0.8 version of Roundcube
+ * Save attachments to temp files before adding to zip archive (memory!)
+
+2011 03 12
+==========
+ * Convert charset for filenames inside zip
+
+2010 08 30
+==========
+ * Get all messages in folder, not just the first page
+
+2010 08 12
+==========
+ * Use $.inArray() instead of Array.indexOf()
+
+2010 08 07
+==========
+ * Add the ability to download a folder as zip
+ * Add the ability to download selection of messages as zip
+ * Add config file to control download options
+
+2010 05 29
+==========
+ * Remove tnef_decode, now done by message class (r3680)
+
+2010 02 21
+==========
+ * First version
\ No newline at end of file
diff --git a/plugins/zipdownload/README b/plugins/zipdownload/README
new file mode 100644
index 0000000..4fa3c17
--- /dev/null
+++ b/plugins/zipdownload/README
@@ -0,0 +1,35 @@
+Roundcube Webmail ZipDownload
+=============================
+This plugin adds an option to download all attachments to a message in one zip
+file, when a message has multiple attachments. The plugin also allows the
+download of a selection of messages in 1 zip file and the download of entire
+folders.
+
+Requirements
+============
+* php_zip extension (including ZipArchive class)
+  Either install it via PECL or for PHP >= 5.2 compile with --enable-zip option
+
+License
+=======
+This plugin is released under the GNU General Public License Version 3
+or later (http://www.gnu.org/licenses/gpl.html).
+
+Even if skins might contain some programming work, they are not considered
+as a linked part of the plugin and therefore skins DO NOT fall under the
+provisions of the GPL license. See the README file located in the core skins
+folder for details on the skin license.
+
+Install
+=======
+* Place this plugin folder into plugins directory of Roundcube
+* Add zipdownload to $rcmail_config['plugins'] in your Roundcube config
+
+NB: When downloading the plugin from GitHub you will need to create a
+directory called zipdownload and place the files in there, ignoring the
+root directory in the downloaded archive
+
+Config
+======
+The default config file is plugins/zipdownload/config.inc.php.dist
+Rename this to plugins/zipdownload/config.inc.php
\ No newline at end of file
diff --git a/plugins/zipdownload/config.inc.php.dist b/plugins/zipdownload/config.inc.php.dist
new file mode 100644
index 0000000..5c7489a
--- /dev/null
+++ b/plugins/zipdownload/config.inc.php.dist
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * ZipDownload configuration file
+ */
+
+// Zip attachments
+// Only show the link when there are more than this many attachments
+// -1 to prevent downloading of attachments as zip
+$rcmail_config['zipdownload_attachments'] = 1;
+
+// Zip entire folders
+$rcmail_config['zipdownload_folder'] = false;
+
+// Zip selection of messages
+$rcmail_config['zipdownload_selection'] = false;
+
+// Charset to use for filenames inside the zip
+$rcmail_config['zipdownload_charset'] = 'ISO-8859-1';
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/ca_ES.inc b/plugins/zipdownload/localization/ca_ES.inc
new file mode 100644
index 0000000..8ccf054
--- /dev/null
+++ b/plugins/zipdownload/localization/ca_ES.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Drakon */
+
+$labels = array();
+$labels['downloadall'] = 'Descarregar tots els adjunts';
+$labels['downloadfolder'] = 'Descarregar carpeta';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/cs_CZ.inc b/plugins/zipdownload/localization/cs_CZ.inc
new file mode 100644
index 0000000..4a1f751
--- /dev/null
+++ b/plugins/zipdownload/localization/cs_CZ.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Tomáš Šafařík */
+
+$labels = array();
+$labels['downloadall'] = 'Stáhnout všechny přílohy';
+$labels['downloadfolder'] = 'Stáhnout složku';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/da_DK.inc b/plugins/zipdownload/localization/da_DK.inc
new file mode 100644
index 0000000..17c7c0d
--- /dev/null
+++ b/plugins/zipdownload/localization/da_DK.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: John Loft Christiansen */
+
+$labels = array();
+$labels['downloadall'] = 'Download alle som .zip-fil';
+$labels['downloadfolder'] = 'Download folder som .zip-fil';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/de_CH.inc b/plugins/zipdownload/localization/de_CH.inc
new file mode 100644
index 0000000..6872560
--- /dev/null
+++ b/plugins/zipdownload/localization/de_CH.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: jedix */
+
+$labels = array();
+$labels['downloadall'] = 'Alle Anhänge herunterladen';
+$labels['downloadfolder'] = 'Ordner herunterladen';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/de_DE.inc b/plugins/zipdownload/localization/de_DE.inc
new file mode 100644
index 0000000..6872560
--- /dev/null
+++ b/plugins/zipdownload/localization/de_DE.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: jedix */
+
+$labels = array();
+$labels['downloadall'] = 'Alle Anhänge herunterladen';
+$labels['downloadfolder'] = 'Ordner herunterladen';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/en_GB.inc b/plugins/zipdownload/localization/en_GB.inc
new file mode 100644
index 0000000..0db6f8f
--- /dev/null
+++ b/plugins/zipdownload/localization/en_GB.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Philip Weir */
+
+$labels = array();
+$labels['downloadall'] = 'Download all attachments';
+$labels['downloadfolder'] = 'Download folder';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/en_US.inc b/plugins/zipdownload/localization/en_US.inc
new file mode 100644
index 0000000..0db6f8f
--- /dev/null
+++ b/plugins/zipdownload/localization/en_US.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Philip Weir */
+
+$labels = array();
+$labels['downloadall'] = 'Download all attachments';
+$labels['downloadfolder'] = 'Download folder';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/es_AR.inc b/plugins/zipdownload/localization/es_AR.inc
new file mode 100644
index 0000000..f3a798c
--- /dev/null
+++ b/plugins/zipdownload/localization/es_AR.inc
@@ -0,0 +1,9 @@
+<?php
+/* Author: gboksar */
+
+$labels = array();
+$labels['downloadall'] = 'Descargar Todo';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/es_ES.inc b/plugins/zipdownload/localization/es_ES.inc
new file mode 100644
index 0000000..193f7b4
--- /dev/null
+++ b/plugins/zipdownload/localization/es_ES.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: David Garabana Barro */
+
+$labels = array();
+$labels['downloadall'] = 'Descargar todos los adjuntos';
+$labels['downloadfolder'] = 'Descargar carpeta';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/et_EE.inc b/plugins/zipdownload/localization/et_EE.inc
new file mode 100644
index 0000000..60a886b
--- /dev/null
+++ b/plugins/zipdownload/localization/et_EE.inc
@@ -0,0 +1,9 @@
+<?php
+/* Author: Henrik Pihl */
+
+$labels = array();
+$labels['downloadall'] = 'Laadi alla kõik manused';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/fr_FR.inc b/plugins/zipdownload/localization/fr_FR.inc
new file mode 100644
index 0000000..b8fc4cd
--- /dev/null
+++ b/plugins/zipdownload/localization/fr_FR.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Olivier Le Brouster */
+
+$labels = array();
+$labels['downloadall'] = 'Télécharger toutes les pièces jointes';
+$labels['downloadfolder'] = 'Télécharger le répertoire';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/gl_ES.inc b/plugins/zipdownload/localization/gl_ES.inc
new file mode 100644
index 0000000..2c91079
--- /dev/null
+++ b/plugins/zipdownload/localization/gl_ES.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: David Garabana Barro */
+
+$labels = array();
+$labels['downloadall'] = 'Descargar tódolos adxuntos';
+$labels['downloadfolder'] = 'Descargar o cartafol';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/hu_HU.inc b/plugins/zipdownload/localization/hu_HU.inc
new file mode 100644
index 0000000..1931cb0
--- /dev/null
+++ b/plugins/zipdownload/localization/hu_HU.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Németh János */
+
+$labels = array();
+$labels['downloadall'] = 'Összes csatolmány letöltése';
+$labels['downloadfolder'] = 'Könyvtár letöltése';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/it_IT.inc b/plugins/zipdownload/localization/it_IT.inc
new file mode 100644
index 0000000..63b7b07
--- /dev/null
+++ b/plugins/zipdownload/localization/it_IT.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Roberto Puzzanghera */
+
+$labels = array();
+$labels['downloadall'] = 'Scarica tutti gli allegati';
+$labels['downloadfolder'] = 'Scarica cartella';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/nl_NL.inc b/plugins/zipdownload/localization/nl_NL.inc
new file mode 100644
index 0000000..1cf32ce
--- /dev/null
+++ b/plugins/zipdownload/localization/nl_NL.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Wouter Kevenaar*/
+
+$labels = array();
+$labels['downloadall'] = 'Alle bijlagen downloaden';
+$labels['downloadfolder'] = 'Map downloaden';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/pl_PL.inc b/plugins/zipdownload/localization/pl_PL.inc
new file mode 100644
index 0000000..c86d875
--- /dev/null
+++ b/plugins/zipdownload/localization/pl_PL.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: DZIOBAK */
+
+$labels = array();
+$labels['downloadall'] = 'Pobierz wszystkie jako ZIP';
+$labels['downloadfolder'] = 'Pobierz folder';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/pt_BR.inc b/plugins/zipdownload/localization/pt_BR.inc
new file mode 100644
index 0000000..c082e54
--- /dev/null
+++ b/plugins/zipdownload/localization/pt_BR.inc
@@ -0,0 +1,9 @@
+<?php
+/* Author: Alexandre Gorges */
+
+$labels = array();
+$labels['downloadall'] = 'Baixar todos os anexos';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/ro_RO.inc b/plugins/zipdownload/localization/ro_RO.inc
new file mode 100644
index 0000000..3a2a1ac
--- /dev/null
+++ b/plugins/zipdownload/localization/ro_RO.inc
@@ -0,0 +1,9 @@
+<?php
+/* Author: Ovidiu Bica */
+
+$labels = array();
+$labels['downloadall'] = 'Descarca toate atasamentele.';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/ru_RU.inc b/plugins/zipdownload/localization/ru_RU.inc
new file mode 100644
index 0000000..ac11327
--- /dev/null
+++ b/plugins/zipdownload/localization/ru_RU.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Peter Zotov */
+
+$labels = array();
+$labels['downloadall'] = 'Загрузить все вложения';
+$labels['downloadfolder'] = 'Загрузить каталог';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/tr_TR.inc b/plugins/zipdownload/localization/tr_TR.inc
new file mode 100644
index 0000000..dc7489d
--- /dev/null
+++ b/plugins/zipdownload/localization/tr_TR.inc
@@ -0,0 +1,10 @@
+<?php
+/* Author: Mustafa Icer */
+
+$labels = array();
+$labels['downloadall'] = 'Tüm ek dosyaları indir';
+$labels['downloadfolder'] = 'klasörü indir';
+
+$messages = array();
+
+?>
\ No newline at end of file
diff --git a/plugins/zipdownload/package.xml b/plugins/zipdownload/package.xml
new file mode 100644
index 0000000..bf55115
--- /dev/null
+++ b/plugins/zipdownload/package.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+		http://pear.php.net/dtd/tasks-1.0.xsd
+		http://pear.php.net/dtd/package-2.0
+		http://pear.php.net/dtd/package-2.0.xsd">
+	<name>zipdownload</name>
+	<channel>pear.roundcube.net</channel>
+	<summary>Download multiple attachments or messages in one zip file</summary>
+	<description>Adds an option to download all attachments to a message in one zip file, when a message has multiple attachments. Also allows the download of a selection of messages in one zip file and the download of entire folders.</description>
+	<lead>
+		<name>Philip Weir</name>
+		<user>JohnDoh</user>
+		<email>roundcube@tehinterweb.co.uk</email>
+		<active>no</active>
+	</lead>
+	<lead>
+		<name>Thomas Bruederli</name>
+		<user>bruederli</user>
+		<email>roundcube@gmail.com</email>
+		<active>yes</active>
+	</lead>
+	<date>2012-09-20</date>
+	<time>19:16:00</time>
+	<version>
+		<release>2.0</release>
+		<api>2.0</api>
+	</version>
+	<stability>
+		<release>stable</release>
+		<api>stable</api>
+	</stability>
+	<license uri="http://www.gnu.org/licenses/gpl.html">GNU GPLv3+</license>
+	<notes>Repo only</notes>
+	<contents>
+		<dir baseinstalldir="/" name="/">
+			<file name="zipdownload.php" role="php">
+				<tasks:replace from="@name@" to="name" type="package-info"/>
+				<tasks:replace from="@package_version@" to="version" type="package-info"/>
+			</file>
+			<file name="zipdownload.js" role="data">
+				<tasks:replace from="@name@" to="name" type="package-info"/>
+				<tasks:replace from="@package_version@" to="version" type="package-info"/>
+			</file>
+			<file name="config.inc.php.dist" role="data"/>
+			<file name="CHANGELOG" role="data"/>
+			<file name="README" role="data"/>
+			<file name="localization/ca_ES.inc" role="data"/>
+			<file name="localization/cs_CZ.inc" role="data"/>
+			<file name="localization/da_DK.inc" role="data"/>
+			<file name="localization/de_CH.inc" role="data"/>
+			<file name="localization/de_DE.inc" role="data"/>
+			<file name="localization/en_GB.inc" role="data"/>
+			<file name="localization/en_US.inc" role="data"/>
+			<file name="localization/es_AR.inc" role="data"/>
+			<file name="localization/es_ES.inc" role="data"/>
+			<file name="localization/et_EE.inc" role="data"/>
+			<file name="localization/fr_FR.inc" role="data"/>
+			<file name="localization/gl_ES.inc" role="data"/>
+			<file name="localization/hu_HU.inc" role="data"/>
+			<file name="localization/it_IT.inc" role="data"/>
+			<file name="localization/nl_NL.inc" role="data"/>
+			<file name="localization/pl_PL.inc" role="data"/>
+			<file name="localization/pt_BR.inc" role="data"/>
+			<file name="localization/ro_RO.inc" role="data"/>
+			<file name="localization/ru_RU.inc" role="data"/>
+			<file name="localization/tr_TR.inc" role="data"/>
+			<file name="skins/classic/zip.png" role="data"/>
+			<file name="skins/classic/zipdownload.css" role="data"/>
+			<file name="skins/larry/zipdownload.css" role="data"/>
+		</dir>
+		<!-- / -->
+	</contents>
+	<dependencies>
+		<required>
+			<php>
+				<min>5.2.1</min>
+			</php>
+			<pearinstaller>
+				<min>1.7.0</min>
+			</pearinstaller>
+			<extension>
+				<name>zip</name>
+				<channel>pecl.php.net</channel>
+				<providesextension>zip</providesextension>
+			</extension>
+		</required>
+	</dependencies>
+	<phprelease/>
+</package>
diff --git a/plugins/zipdownload/skins/classic/zip.png b/plugins/zipdownload/skins/classic/zip.png
new file mode 100644
index 0000000..c64fde8
--- /dev/null
+++ b/plugins/zipdownload/skins/classic/zip.png
Binary files differ
diff --git a/plugins/zipdownload/skins/classic/zipdownload.css b/plugins/zipdownload/skins/classic/zipdownload.css
new file mode 100644
index 0000000..2608fdf
--- /dev/null
+++ b/plugins/zipdownload/skins/classic/zipdownload.css
@@ -0,0 +1,8 @@
+/* Roundcube Zipdownload plugin styles for classic skin */
+
+a.zipdownload {
+	display: inline-block;
+	padding: 0 0 2px 20px;
+	background: url(zip.png) 0 1px no-repeat;
+	font-style: italic;
+}
diff --git a/plugins/zipdownload/skins/larry/zipdownload.css b/plugins/zipdownload/skins/larry/zipdownload.css
new file mode 100644
index 0000000..d719ac6
--- /dev/null
+++ b/plugins/zipdownload/skins/larry/zipdownload.css
@@ -0,0 +1,7 @@
+/* Roundcube Zipdownload plugin styles for skin "Larry" */
+
+a.zipdownload {
+	display: inline-block;
+	margin-top: 1.5em;
+	padding: 3px 5px 4px 5px;
+}
\ No newline at end of file
diff --git a/plugins/zipdownload/zipdownload.js b/plugins/zipdownload/zipdownload.js
new file mode 100644
index 0000000..080dcd9
--- /dev/null
+++ b/plugins/zipdownload/zipdownload.js
@@ -0,0 +1,33 @@
+/**
+ * ZipDownload plugin script
+ */
+
+function rcmail_zipmessages() {
+	if (rcmail.message_list && rcmail.message_list.get_selection().length > 1) {
+		rcmail.goto_url('plugin.zipdownload.zip_messages', '_mbox=' + urlencode(rcmail.env.mailbox) + '&_uid=' + rcmail.message_list.get_selection().join(','));
+	}
+}
+
+$(document).ready(function() {
+	if (window.rcmail) {
+		rcmail.addEventListener('init', function(evt) {
+			// register command (directly enable in message view mode)
+			rcmail.register_command('plugin.zipdownload.zip_folder', function() {
+				rcmail.goto_url('plugin.zipdownload.zip_folder', '_mbox=' + urlencode(rcmail.env.mailbox));
+			}, rcmail.env.messagecount > 0);
+
+			if (rcmail.message_list && rcmail.env.zipdownload_selection) {
+				rcmail.message_list.addEventListener('select', function(list) {
+					rcmail.enable_command('download', list.get_selection().length > 0);
+				});
+
+				// check in contextmenu plugin exists and if so allow multiple message download
+				if (rcmail.contextmenu_disable_multi)
+					rcmail.contextmenu_disable_multi.splice($.inArray('#download', rcmail.contextmenu_disable_multi), 1);
+			}
+		});
+
+		rcmail.addEventListener('listupdate', function(props) { rcmail.enable_command('plugin.zipdownload.zip_folder', rcmail.env.messagecount > 0); } );
+		rcmail.addEventListener('beforedownload', function(props) { rcmail_zipmessages(); } );
+	}
+});
\ No newline at end of file
diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php
new file mode 100644
index 0000000..8bad9b3
--- /dev/null
+++ b/plugins/zipdownload/zipdownload.php
@@ -0,0 +1,267 @@
+<?php
+
+/**
+ * ZipDownload
+ *
+ * Plugin to allow the download of all message attachments in one zip file
+ *
+ * @version @package_version@
+ * @requires php_zip extension (including ZipArchive class)
+ * @author Philip Weir
+ * @author Thomas Bruderli
+ */
+class zipdownload extends rcube_plugin
+{
+	public $task = 'mail';
+	private $charset = 'ASCII';
+
+	/**
+	 * Plugin initialization
+	 */
+	public function init()
+	{
+		// check requirements first
+		if (!class_exists('ZipArchive', false)) {
+			rcmail::raise_error(array(
+				'code' => 520, 'type' => 'php',
+				'file' => __FILE__, 'line' => __LINE__,
+				'message' => "php_zip extension is required for the zipdownload plugin"), true, false);
+			return;
+		}
+
+		$rcmail = rcmail::get_instance();
+		$this->charset = $rcmail->config->get('zipdownload_charset', RCMAIL_CHARSET);
+
+		$this->load_config();
+		$this->add_texts('localization');
+
+		if ($rcmail->config->get('zipdownload_attachments', 1) > -1 && ($rcmail->action == 'show' || $rcmail->action == 'preview'))
+			$this->add_hook('template_object_messageattachments', array($this, 'attachment_ziplink'));
+
+		$this->register_action('plugin.zipdownload.zip_attachments', array($this, 'download_attachments'));
+		$this->register_action('plugin.zipdownload.zip_messages', array($this, 'download_selection'));
+		$this->register_action('plugin.zipdownload.zip_folder', array($this, 'download_folder'));
+
+		if ($rcmail->config->get('zipdownload_folder', false) || $rcmail->config->get('zipdownload_selection', false)) {
+			$this->include_script('zipdownload.js');
+			$this->api->output->set_env('zipdownload_selection', $rcmail->config->get('zipdownload_selection', false));
+
+			if ($rcmail->config->get('zipdownload_folder', false) && ($rcmail->action == '' || $rcmail->action == 'show')) {
+				$zipdownload = $this->api->output->button(array('command' => 'plugin.zipdownload.zip_folder', 'type' => 'link', 'classact' => 'active', 'content' => $this->gettext('downloadfolder')));
+				$this->api->add_content(html::tag('li', array('class' => 'separator_above'), $zipdownload), 'mailboxoptions');
+			}
+		}
+	}
+
+	/**
+	 * Place a link/button after attachments listing to trigger download
+	 */
+	public function attachment_ziplink($p)
+	{
+		$rcmail = rcmail::get_instance();
+
+		// only show the link if there is more than the configured number of attachments
+		if (substr_count($p['content'], '<li') > $rcmail->config->get('zipdownload_attachments', 1)) {
+			$link = html::a(array(
+				'href' => rcmail_url('plugin.zipdownload.zip_attachments', array('_mbox' => $rcmail->output->env['mailbox'], '_uid' => $rcmail->output->env['uid'])),
+				'class' => 'button zipdownload',
+				),
+				Q($this->gettext('downloadall'))
+			);
+
+			// append link to attachments list, slightly different in some skins
+			switch (rcmail::get_instance()->config->get('skin')) {
+				case 'classic':
+					$p['content'] = str_replace('</ul>', html::tag('li', array('class' => 'zipdownload'), $link) . '</ul>', $p['content']);
+					break;
+
+				default:
+					$p['content'] .= $link;
+					break;
+			}
+
+			$this->include_stylesheet($this->local_skin_path() . '/zipdownload.css');
+		}
+
+		return $p;
+	}
+
+	/**
+	 * Handler for attachment download action
+	 */
+	public function download_attachments()
+	{
+		$rcmail = rcmail::get_instance();
+		$imap = $rcmail->storage;
+		$temp_dir = $rcmail->config->get('temp_dir');
+		$tmpfname = tempnam($temp_dir, 'zipdownload');
+		$tempfiles = array($tmpfname);
+		$message = new rcube_message(get_input_value('_uid', RCUBE_INPUT_GET));
+
+		// open zip file
+		$zip = new ZipArchive();
+		$zip->open($tmpfname, ZIPARCHIVE::OVERWRITE);
+
+		foreach ($message->attachments as $part) {
+			$pid = $part->mime_id;
+			$part = $message->mime_parts[$pid];
+			$disp_name = $this->_convert_filename($part->filename, $part->charset);
+
+			if ($part->body) {
+				$orig_message_raw = $part->body;
+				$zip->addFromString($disp_name, $orig_message_raw);
+			}
+			else {
+				$tmpfn = tempnam($temp_dir, 'zipattach');
+				$tmpfp = fopen($tmpfn, 'w');
+				$imap->get_message_part($message->uid, $part->mime_id, $part, null, $tmpfp, true);
+				$tempfiles[] = $tmpfn;
+				fclose($tmpfp);
+				$zip->addFile($tmpfn, $disp_name);
+			}
+
+		}
+
+		$zip->close();
+
+		$filename = ($message->subject ? $message->subject : 'roundcube') . '.zip';
+		$this->_deliver_zipfile($tmpfname, $filename);
+
+		// delete temporary files from disk
+		foreach ($tempfiles as $tmpfn)
+			unlink($tmpfn);
+
+		exit;
+	}
+
+	/**
+	 * Handler for message download action
+	 */
+	public function download_selection()
+	{
+		if (isset($_REQUEST['_uid'])) {
+			$uids = explode(",", get_input_value('_uid', RCUBE_INPUT_GPC));
+
+			if (sizeof($uids) > 0)
+				$this->_download_messages($uids);
+		}
+	}
+
+	/**
+	 * Handler for folder download action
+	 */
+	public function download_folder()
+	{
+		$imap = rcmail::get_instance()->storage;
+		$mbox_name = $imap->get_folder();
+
+		// initialize searching result if search_filter is used
+		if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') {
+			$imap->search($mbox_name, $_SESSION['search_filter'], RCMAIL_CHARSET);
+		}
+
+		// fetch message headers for all pages
+		$uids = array();
+		if ($count = $imap->count($mbox_name, $imap->get_threading() ? 'THREADS' : 'ALL', FALSE)) {
+			for ($i = 0; ($i * $imap->get_pagesize()) <= $count; $i++) {
+				$a_headers = $imap->list_messages($mbox_name, ($i + 1));
+
+				foreach ($a_headers as $n => $header) {
+					if (empty($header))
+						continue;
+
+					array_push($uids, $header->uid);
+				}
+			}
+		}
+
+		if (sizeof($uids) > 0)
+			$this->_download_messages($uids);
+	}
+
+	/**
+	 * Helper method to packs all the given messages into a zip archive
+	 *
+	 * @param array List of message UIDs to download
+	 */
+	private function _download_messages($uids)
+	{
+		$rcmail = rcmail::get_instance();
+		$imap = $rcmail->storage;
+		$temp_dir = $rcmail->config->get('temp_dir');
+		$tmpfname = tempnam($temp_dir, 'zipdownload');
+		$tempfiles = array($tmpfname);
+
+		// open zip file
+		$zip = new ZipArchive();
+		$zip->open($tmpfname, ZIPARCHIVE::OVERWRITE);
+
+		foreach ($uids as $key => $uid){
+			$headers = $imap->get_message_headers($uid);
+			$subject = rcube_mime::decode_mime_string((string)$headers->subject);
+			$subject = $this->_convert_filename($subject);
+			$subject = substr($subject, 0, 16);
+
+			if (isset($subject) && $subject !="")
+				$disp_name = $subject . ".eml";
+			else
+				$disp_name = "message_rfc822.eml";
+
+			$disp_name = $uid . "_" . $disp_name;
+			
+			$tmpfn = tempnam($temp_dir, 'zipmessage');
+			$tmpfp = fopen($tmpfn, 'w');
+			$imap->get_raw_body($uid, $tmpfp);
+			$tempfiles[] = $tmpfn;
+			fclose($tmpfp);
+			$zip->addFile($tmpfn, $disp_name);
+		}
+
+		$zip->close();
+
+		$this->_deliver_zipfile($tmpfname, $imap->get_folder() . '.zip');
+
+		// delete temporary files from disk
+		foreach ($tempfiles as $tmpfn)
+			unlink($tmpfn);
+
+		exit;
+	}
+
+	/**
+	 * Helper method to send the zip archive to the browser
+	 */
+	private function _deliver_zipfile($tmpfname, $filename)
+	{
+		$browser = new rcube_browser;
+		send_nocacheing_headers();
+
+		if ($browser->ie && $browser->ver < 7)
+			$filename = rawurlencode(abbreviate_string($filename, 55));
+		else if ($browser->ie)
+			$filename = rawurlencode($filename);
+		else
+			$filename = addcslashes($filename, '"');
+
+		// send download headers
+		header("Content-Type: application/octet-stream");
+		if ($browser->ie)
+			header("Content-Type: application/force-download");
+
+		// don't kill the connection if download takes more than 30 sec.
+		@set_time_limit(0);
+		header("Content-Disposition: attachment; filename=\"". $filename ."\"");
+		header("Content-length: " . filesize($tmpfname));
+		readfile($tmpfname);
+	}
+
+	/**
+	 * Helper function to convert filenames to the configured charset
+	 */
+	private function _convert_filename($str, $from = RCMAIL_CHARSET)
+	{
+		return strtr(rcube_charset_convert($str, $from, $this->charset), array(':'=>'', '/'=>'-'));
+	}
+}
+
+?>
\ No newline at end of file
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 b278431..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;
         }
 
@@ -682,7 +679,7 @@
         // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
         if (strpos($name, '%s') !== false) {
             $user_email = self::get_input_value('_user', self::INPUT_POST);
-            $user_email = rcube_utils::idn_convert($user_email, true);
+            $user_email = self::idn_convert($user_email, true);
             $matches    = preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s);
             if ($matches < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false) {
                 return false;
diff --git a/program/lib/html2text.php b/program/lib/html2text.php
index 28c5ae0..dd413e0 100644
--- a/program/lib/html2text.php
+++ b/program/lib/html2text.php
@@ -145,6 +145,7 @@
     var $search = array(
         "/\r/",                                  // Non-legal carriage return
         "/[\n\t]+/",                             // Newlines and tabs
+        '/<head[^>]*>.*?<\/head>/i',             // <head>
         '/<script[^>]*>.*?<\/script>/i',         // <script>s -- which strip_tags supposedly has problems with
         '/<style[^>]*>.*?<\/style>/i',           // <style>s -- which strip_tags supposedly has problems with
         '/<p[^>]*>/i',                           // <P>
@@ -172,6 +173,7 @@
     var $replace = array(
         '',                                     // Non-legal carriage return
         ' ',                                    // Newlines and tabs
+        '',                                     // <head>
         '',                                     // <script>s -- which strip_tags supposedly has problems with
         '',                                     // <style>s -- which strip_tags supposedly has problems with
         "\n\n",                                 // <P>
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