diff options
27 files changed, 971 insertions, 179 deletions
@@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== + - Fix handling of HTML form elements in messages (#1485137) - Fix regression in setting recipient to self when replying to a Sent message (#1487074) - Fix listing of folders in hidden namespaces (#1486796) diff --git a/plugins/managesieve/Changelog b/plugins/managesieve/Changelog index 185510410..d7ef8e561 100644 --- a/plugins/managesieve/Changelog +++ b/plugins/managesieve/Changelog @@ -1,4 +1,16 @@ +- Fixed setting test type to :is when none is specified + +* version 5.0-rc1 [2011-11-17] +----------------------------------------------------------- - Fixed sorting of scripts, scripts including aware of the sort order +- Fixed import of rules with unsupported tests +- Added 'address' and 'envelope' tests support +- Added 'body' extension support (RFC5173) +- Added 'subaddress' extension support (RFC5233) +- Added comparators support +- Changed Sender/Recipient labels to From/To +- Fixed importing rule names from Ingo +- Fixed handling of extensions disabled in config * version 5.0-beta [2011-10-17] ----------------------------------------------------------- diff --git a/plugins/managesieve/lib/rcube_sieve.php b/plugins/managesieve/lib/rcube_sieve.php index 7b7ea6eb6..2ed2e54bf 100644 --- a/plugins/managesieve/lib/rcube_sieve.php +++ b/plugins/managesieve/lib/rcube_sieve.php @@ -44,7 +44,6 @@ class rcube_sieve public $script; // rcube_sieve_script object public $current; // name of currently loaded script - private $disabled; // array of disabled extensions private $exts; // array of supported extensions @@ -89,7 +88,17 @@ class rcube_sieve } $this->exts = $this->get_extensions(); - $this->disabled = $disabled; + + // disable features by config + if (!empty($disabled)) { + // we're working on lower-cased names + $disabled = array_map('strtolower', (array) $disabled); + foreach ($disabled as $ext) { + if (($idx = array_search($ext, $this->exts)) !== false) { + unset($this->exts[$idx]); + } + } + } } public function __destruct() { @@ -301,7 +310,7 @@ class rcube_sieve private function _parse($txt) { // parse - $script = new rcube_sieve_script($txt, $this->disabled, $this->exts); + $script = new rcube_sieve_script($txt, $this->exts); // fix/convert to Roundcube format if (!empty($script->content)) { diff --git a/plugins/managesieve/lib/rcube_sieve_script.php b/plugins/managesieve/lib/rcube_sieve_script.php index 3c6993dcc..f5ad62c3f 100644 --- a/plugins/managesieve/lib/rcube_sieve_script.php +++ b/plugins/managesieve/lib/rcube_sieve_script.php @@ -29,9 +29,9 @@ class rcube_sieve_script private $vars = array(); // "global" variables private $prefix = ''; // script header (comments) - private $capabilities = array(); // Sieve extensions supported by server private $supported = array( // Sieve extensions supported by class - 'fileinto', // RFC3028 + 'fileinto', // RFC5228 + 'envelope', // RFC5228 'reject', // RFC5429 'ereject', // RFC5429 'copy', // RFC3894 @@ -42,30 +42,30 @@ class rcube_sieve_script 'imap4flags', // RFC5232 'include', // draft-ietf-sieve-include-12 'variables', // RFC5229 - // TODO: body, notify + 'body', // RFC5173 + 'subaddress', // RFC5233 + // @TODO: enotify/notify, spamtest+virustest, mailbox, date ); /** * Object constructor * * @param string Script's text content - * @param array List of disabled extensions * @param array List of capabilities supported by server */ - public function __construct($script, $disabled=array(), $capabilities=array()) + public function __construct($script, $capabilities=array()) { - if (!empty($disabled)) { - // we're working on lower-cased names - $disabled = array_map('strtolower', (array) $disabled); - foreach ($disabled as $ext) { - if (($idx = array_search($ext, $this->supported)) !== false) { + $capabilities = array_map('strtolower', (array) $capabilities); + + // disable features by server capabilities + if (!empty($capabilities)) { + foreach ($this->supported as $idx => $ext) { + if (!in_array($ext, $capabilities)) { unset($this->supported[$idx]); } } } - $this->capabilities = array_map('strtolower', (array) $capabilities); - // Parse text content of the script $this->_parse_text($script); } @@ -182,7 +182,7 @@ class rcube_sieve_script $idx = 0; if (!empty($this->vars)) { - if (in_array('variables', (array)$this->capabilities)) { + if (in_array('variables', (array)$this->supported)) { $has_vars = true; array_push($exts, 'variables'); } @@ -222,34 +222,96 @@ class rcube_sieve_script $tests[$i] .= ($test['not'] ? 'not ' : ''); $tests[$i] .= 'size :' . ($test['type']=='under' ? 'under ' : 'over ') . $test['arg']; break; + case 'true': $tests[$i] .= ($test['not'] ? 'false' : 'true'); break; + case 'exists': $tests[$i] .= ($test['not'] ? 'not ' : ''); $tests[$i] .= 'exists ' . self::escape_string($test['arg']); break; + case 'header': $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= 'header'; - // relational operator + comparator - if (preg_match('/^(value|count)-([gteqnl]{2})/', $test['type'], $m)) { - array_push($exts, 'relational'); - array_push($exts, 'comparator-i;ascii-numeric'); + if (!empty($test['type'])) { + // relational operator + comparator + if (preg_match('/^(value|count)-([gteqnl]{2})/', $test['type'], $m)) { + array_push($exts, 'relational'); + array_push($exts, 'comparator-i;ascii-numeric'); - $tests[$i] .= 'header :' . $m[1] . ' "' . $m[2] . '" :comparator "i;ascii-numeric"'; + $tests[$i] .= ' :' . $m[1] . ' "' . $m[2] . '" :comparator "i;ascii-numeric"'; + } + else { + $this->add_comparator($test, $tests[$i], $exts); + + if ($test['type'] == 'regex') { + array_push($exts, 'regex'); + } + + $tests[$i] .= ' :' . $test['type']; + } } - else { + + $tests[$i] .= ' ' . self::escape_string($test['arg1']); + $tests[$i] .= ' ' . self::escape_string($test['arg2']); + break; + + case 'address': + case 'envelope': + if ($test['test'] == 'envelope') { + array_push($exts, 'envelope'); + } + + $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= $test['test']; + + if (!empty($test['part'])) { + $tests[$i] .= ' :' . $test['part']; + if ($test['part'] == 'user' || $test['part'] == 'detail') { + array_push($exts, 'subaddress'); + } + } + + $this->add_comparator($test, $tests[$i], $exts); + + if (!empty($test['type'])) { if ($test['type'] == 'regex') { array_push($exts, 'regex'); } - - $tests[$i] .= 'header :' . $test['type']; + $tests[$i] .= ' :' . $test['type']; } $tests[$i] .= ' ' . self::escape_string($test['arg1']); $tests[$i] .= ' ' . self::escape_string($test['arg2']); break; + + case 'body': + array_push($exts, 'body'); + + $tests[$i] .= ($test['not'] ? 'not ' : '') . 'body'; + + $this->add_comparator($test, $tests[$i], $exts); + + if (!empty($test['part'])) { + $tests[$i] .= ' :' . $test['part']; + + if (!empty($test['content']) && $test['part'] == 'content') { + $tests[$i] .= ' ' . self::escape_string($test['content']); + } + } + + if (!empty($test['type'])) { + if ($test['type'] == 'regex') { + array_push($exts, 'regex'); + } + $tests[$i] .= ' :' . $test['type']; + } + + $tests[$i] .= ' ' . self::escape_string($test['arg']); + break; } $i++; } @@ -311,7 +373,7 @@ class rcube_sieve_script case 'addflag': case 'setflag': case 'removeflag': - if (is_array($this->capabilities) && in_array('imap4flags', $this->capabilities)) + if (in_array('imap4flags', $this->supported)) array_push($exts, 'imap4flags'); else array_push($exts, 'imapflags'); @@ -448,7 +510,7 @@ class rcube_sieve_script // handle script header if (empty($options['prefix'])) { $options['prefix'] = true; - if ($prefix && strpos($prefix, 'Generated by Ingo')) { + if ($prefix && strpos($prefix, 'horde.org/ingo')) { $options['format'] = 'INGO'; } } @@ -559,7 +621,7 @@ class rcube_sieve_script $header = array('test' => 'header', 'not' => $not, 'arg1' => '', 'arg2' => ''); for ($i=0, $len=count($tokens); $i<$len; $i++) { if (!is_array($tokens[$i]) && preg_match('/^:comparator$/i', $tokens[$i])) { - $i++; + $header['comparator'] = $tokens[++$i]; } else if (!is_array($tokens[$i]) && preg_match('/^:(count|value)$/i', $tokens[$i])) { $header['type'] = strtolower(substr($tokens[$i], 1)) . '-' . $tokens[++$i]; @@ -576,6 +638,52 @@ class rcube_sieve_script $tests[] = $header; break; + case 'address': + case 'envelope': + $header = array('test' => $token, 'not' => $not, 'arg1' => '', 'arg2' => ''); + for ($i=0, $len=count($tokens); $i<$len; $i++) { + if (!is_array($tokens[$i]) && preg_match('/^:comparator$/i', $tokens[$i])) { + $header['comparator'] = $tokens[++$i]; + } + else if (!is_array($tokens[$i]) && preg_match('/^:(is|contains|matches|regex)$/i', $tokens[$i])) { + $header['type'] = strtolower(substr($tokens[$i], 1)); + } + else if (!is_array($tokens[$i]) && preg_match('/^:(localpart|domain|all|user|detail)$/i', $tokens[$i])) { + $header['part'] = strtolower(substr($tokens[$i], 1)); + } + else { + $header['arg1'] = $header['arg2']; + $header['arg2'] = $tokens[$i]; + } + } + + $tests[] = $header; + break; + + case 'body': + $header = array('test' => 'body', 'not' => $not, 'arg' => ''); + for ($i=0, $len=count($tokens); $i<$len; $i++) { + if (!is_array($tokens[$i]) && preg_match('/^:comparator$/i', $tokens[$i])) { + $header['comparator'] = $tokens[++$i]; + } + else if (!is_array($tokens[$i]) && preg_match('/^:(is|contains|matches|regex)$/i', $tokens[$i])) { + $header['type'] = strtolower(substr($tokens[$i], 1)); + } + else if (!is_array($tokens[$i]) && preg_match('/^:(raw|content|text)$/i', $tokens[$i])) { + $header['part'] = strtolower(substr($tokens[$i], 1)); + + if ($header['part'] == 'content') { + $header['content'] = $tokens[++$i]; + } + } + else { + $header['arg'] = $tokens[$i]; + } + } + + $tests[] = $header; + break; + case 'exists': $tests[] = array('test' => 'exists', 'not' => $not, 'arg' => array_pop($tokens)); @@ -597,9 +705,7 @@ class rcube_sieve_script } // ...and actions block - if ($tests) { - $actions = $this->_parse_actions($content); - } + $actions = $this->_parse_actions($content); if ($tests && $actions) { $result = array( @@ -747,6 +853,29 @@ class rcube_sieve_script } /** + * + */ + private function add_comparator($test, &$out, &$exts) + { + if (empty($test['comparator'])) { + return; + } + + if ($test['comparator'] == 'i;ascii-numeric') { + array_push($exts, 'relational'); + array_push($exts, 'comparator-i;ascii-numeric'); + } + else if (!in_array($test['comparator'], array('i;octet', 'i;ascii-casemap'))) { + array_push($exts, 'comparator-' . $test['comparator']); + } + + // skip default comparator + if ($test['comparator'] != 'i;ascii-casemap') { + $out .= ' :comparator ' . self::escape_string($test['comparator']); + } + } + + /** * Escape special chars into quoted string value or multi-line string * or list of strings * diff --git a/plugins/managesieve/localization/en_US.inc b/plugins/managesieve/localization/en_US.inc index 24cd72a86..94e0ba60c 100644 --- a/plugins/managesieve/localization/en_US.inc +++ b/plugins/managesieve/localization/en_US.inc @@ -82,6 +82,25 @@ $labels['filtercreate'] = 'Create filter'; $labels['usedata'] = 'Use following data in the filter:'; $labels['nextstep'] = 'Next Step'; $labels['...'] = '...'; +$labels['advancedopts'] = 'Advanced options'; +$labels['body'] = 'Body'; +$labels['address'] = 'address'; +$labels['envelope'] = 'envelope'; +$labels['modifier'] = 'modifier:'; +$labels['text'] = 'text'; +$labels['undecoded'] = 'undecoded (raw)'; +$labels['contenttype'] = 'content type'; +$labels['modtype'] = 'type:'; +$labels['allparts'] = 'all'; +$labels['domain'] = 'domain'; +$labels['localpart'] = 'local part'; +$labels['user'] = 'user'; +$labels['detail'] = 'detail'; +$labels['comparator'] = 'comparator:'; +$labels['default'] = 'default'; +$labels['octet'] = 'strict (octet)'; +$labels['asciicasemap'] = 'case insensitive (ascii-casemap)'; +$labels['asciinumeric'] = 'numeric (ascii-numeric)'; $messages = array(); $messages['filterunknownerror'] = 'Unknown server error.'; diff --git a/plugins/managesieve/localization/pl_PL.inc b/plugins/managesieve/localization/pl_PL.inc index eaa09e198..c0ec2ede2 100644 --- a/plugins/managesieve/localization/pl_PL.inc +++ b/plugins/managesieve/localization/pl_PL.inc @@ -81,6 +81,25 @@ $labels['filtercreate'] = 'Utwóż filtr'; $labels['usedata'] = 'Użyj następujących danych do utworzenia filtra:'; $labels['nextstep'] = 'Następny krok'; $labels['...'] = '...'; +$labels['advancedopts'] = 'Zaawansowane opcje'; +$labels['body'] = 'Treść'; +$labels['address'] = 'adres'; +$labels['envelope'] = 'koperta (envelope)'; +$labels['modifier'] = 'modyfikator:'; +$labels['text'] = 'tekst'; +$labels['undecoded'] = 'nie (raw)'; +$labels['contenttype'] = 'typ części (content type)'; +$labels['modtype'] = 'typ:'; +$labels['allparts'] = 'wszystkie'; +$labels['domain'] = 'domena'; +$labels['localpart'] = 'część lokalna'; +$labels['user'] = 'użytkownik'; +$labels['detail'] = 'detal'; +$labels['comparator'] = 'komparator:'; +$labels['default'] = 'domyślny'; +$labels['octet'] = 'dokładny (octet)'; +$labels['asciicasemap'] = 'nierozróżniający wielkości liter (ascii-casemap)'; +$labels['asciinumeric'] = 'numeryczny (ascii-numeric)'; $messages = array(); $messages['filterunknownerror'] = 'Nieznany błąd serwera.'; diff --git a/plugins/managesieve/managesieve.js b/plugins/managesieve/managesieve.js index 5f9c67004..a7b15f7b5 100644 --- a/plugins/managesieve/managesieve.js +++ b/plugins/managesieve/managesieve.js @@ -542,19 +542,28 @@ function rule_header_select(id) size = document.getElementById('rule_size' + id), op = document.getElementById('rule_op' + id), target = document.getElementById('rule_target' + id), - header = document.getElementById('custom_header' + id); + header = document.getElementById('custom_header' + id), + mod = document.getElementById('rule_mod' + id), + trans = document.getElementById('rule_trans' + id), + comp = document.getElementById('rule_comp' + id); if (obj.value == 'size') { size.style.display = 'inline'; op.style.display = 'none'; target.style.display = 'none'; header.style.display = 'none'; + mod.style.display = 'none'; + trans.style.display = 'none'; + comp.style.display = 'none'; } else { header.style.display = obj.value != '...' ? 'none' : 'inline'; size.style.display = 'none'; op.style.display = 'inline'; + comp.style.display = ''; rule_op_select(id); + mod.style.display = obj.value == 'body' ? 'none' : 'block'; + trans.style.display = obj.value == 'body' ? 'block' : 'none'; } obj.style.width = obj.value == '...' ? '40px' : ''; @@ -568,11 +577,41 @@ function rule_op_select(id) target.style.display = obj.value == 'exists' || obj.value == 'notexists' ? 'none' : 'inline'; }; +function rule_trans_select(id) +{ + var obj = document.getElementById('rule_trans_op' + id), + target = document.getElementById('rule_trans_type' + id); + + target.style.display = obj.value != 'content' ? 'none' : 'inline'; +}; + +function rule_mod_select(id) +{ + var obj = document.getElementById('rule_mod_op' + id), + target = document.getElementById('rule_mod_type' + id); + + target.style.display = obj.value != 'address' && obj.value != 'envelope' ? 'none' : 'inline'; +}; + function rule_join_radio(value) { $('#rules').css('display', value == 'any' ? 'none' : 'block'); }; +function rule_adv_switch(id, elem) +{ + var elem = $(elem), enabled = elem.hasClass('hide'), adv = $('#rule_advanced'+id); + + if (enabled) { + adv.hide(); + elem.removeClass('hide').addClass('show'); + } + else { + adv.show(); + elem.removeClass('show').addClass('hide'); + } +} + function action_type_select(id) { var obj = document.getElementById('action_type' + id), diff --git a/plugins/managesieve/managesieve.php b/plugins/managesieve/managesieve.php index 0787c7642..5ac406ade 100644 --- a/plugins/managesieve/managesieve.php +++ b/plugins/managesieve/managesieve.php @@ -45,9 +45,23 @@ class managesieve extends rcube_plugin private $list; private $active = array(); private $headers = array( - 'subject' => 'Subject', - 'sender' => 'From', - 'recipient' => 'To', + 'subject' => 'Subject', + 'from' => 'From', + 'to' => 'To', + ); + private $addr_headers = array( + // Required + "from", "to", "cc", "bcc", "sender", "resent-from", "resent-to", + // Additional (RFC 822 / RFC 2822) + "reply-to", "resent-reply-to", "resent-sender", "resent-cc", "resent-bcc", + // Non-standard (RFC 2076, draft-palme-mailext-headers-08.txt) + "for-approval", "for-handling", "for-comment", "apparently-to", "errors-to", + "delivered-to", "return-receipt-to", "x-admin", "read-receipt-to", + "x-confirm-reading-to", "return-receipt-requested", + "registered-mail-reply-requested-by", "mail-followup-to", "mail-reply-to", + "abuse-reports-to", "x-complaints-to", "x-report-abuse-to", + // Undocumented + "x-beenthere", ); const VERSION = '5.0'; @@ -588,6 +602,11 @@ class managesieve extends rcube_plugin $sizeitems = get_input_value('_rule_size_item', RCUBE_INPUT_POST); $sizetargets = get_input_value('_rule_size_target', RCUBE_INPUT_POST); $targets = get_input_value('_rule_target', RCUBE_INPUT_POST, true); + $mods = get_input_value('_rule_mod', RCUBE_INPUT_POST); + $mod_types = get_input_value('_rule_mod_type', RCUBE_INPUT_POST); + $body_trans = get_input_value('_rule_trans', RCUBE_INPUT_POST); + $body_types = get_input_value('_rule_trans_type', RCUBE_INPUT_POST, true); + $comparators = get_input_value('_rule_comp', RCUBE_INPUT_POST); $act_types = get_input_value('_action_type', RCUBE_INPUT_POST, true); $mailboxes = get_input_value('_action_mailbox', RCUBE_INPUT_POST, true); $act_targets = get_input_value('_action_target', RCUBE_INPUT_POST, true); @@ -625,23 +644,101 @@ class managesieve extends rcube_plugin } else { foreach ($headers as $idx => $header) { - $header = $this->strip_value($header); - $target = $this->strip_value($targets[$idx], true); - $op = $this->strip_value($ops[$idx]); + $header = $this->strip_value($header); + $target = $this->strip_value($targets[$idx], true); + $operator = $this->strip_value($ops[$idx]); + $comparator = $this->strip_value($comparators[$idx]); + + if ($header == 'size') { + $sizeop = $this->strip_value($sizeops[$idx]); + $sizeitem = $this->strip_value($items[$idx]); + $sizetarget = $this->strip_value($sizetargets[$idx]); + + $this->form['tests'][$i]['test'] = 'size'; + $this->form['tests'][$i]['type'] = $sizeop; + $this->form['tests'][$i]['arg'] = $sizetarget; + + if ($sizetarget == '') + $this->errors['tests'][$i]['sizetarget'] = $this->gettext('cannotbeempty'); + else if (!preg_match('/^[0-9]+(K|M|G)?$/i', $sizetarget.$sizeitem, $m)) { + $this->errors['tests'][$i]['sizetarget'] = $this->gettext('forbiddenchars'); + $this->form['tests'][$i]['item'] = $sizeitem; + } + else + $this->form['tests'][$i]['arg'] .= $m[1]; + } + else if ($header == 'body') { + $trans = $this->strip_value($body_trans[$idx]); + $trans_type = $this->strip_value($body_types[$idx], true); - // normal header - if (in_array($header, $this->headers)) { - if (preg_match('/^not/', $op)) + if (preg_match('/^not/', $operator)) $this->form['tests'][$i]['not'] = true; - $type = preg_replace('/^not/', '', $op); + $type = preg_replace('/^not/', '', $operator); + + if ($type == 'exists') { + $this->errors['tests'][$i]['op'] = true; + } + + $this->form['tests'][$i]['test'] = 'body'; + $this->form['tests'][$i]['type'] = $type; + $this->form['tests'][$i]['arg'] = $target; + + if ($target == '' && $type != 'exists') + $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty'); + else if (preg_match('/^(value|count)-/', $type) && !preg_match('/[0-9]+/', $target)) + $this->errors['tests'][$i]['target'] = $this->gettext('forbiddenchars'); + + $this->form['tests'][$i]['part'] = $trans; + if ($trans == 'content') { + $this->form['tests'][$i]['content'] = $trans_type; + } + } + else { + $cust_header = $headers = $this->strip_value($cust_headers[$idx]); + $mod = $this->strip_value($mods[$idx]); + $mod_type = $this->strip_value($mod_types[$idx]); + + if (preg_match('/^not/', $operator)) + $this->form['tests'][$i]['not'] = true; + $type = preg_replace('/^not/', '', $operator); + + if ($header == '...') { + $headers = preg_split('/[\s,]+/', $cust_header, -1, PREG_SPLIT_NO_EMPTY); + + if (!count($headers)) + $this->errors['tests'][$i]['header'] = $this->gettext('cannotbeempty'); + else { + foreach ($headers as $hr) + if (!preg_match('/^[a-z0-9-]+$/i', $hr)) + $this->errors['tests'][$i]['header'] = $this->gettext('forbiddenchars'); + } + + if (empty($this->errors['tests'][$i]['header'])) + $cust_header = (is_array($headers) && count($headers) == 1) ? $headers[0] : $headers; + } if ($type == 'exists') { $this->form['tests'][$i]['test'] = 'exists'; - $this->form['tests'][$i]['arg'] = $header; + $this->form['tests'][$i]['arg'] = $header == '...' ? $cust_header : $header; } else { + $test = 'header'; + $header = $header == '...' ? $cust_header : $header; + + if ($mod == 'address' || $mod == 'envelope') { + $found = false; + if (empty($this->errors['tests'][$i]['header'])) { + foreach ((array)$header as $hdr) { + if (!in_array(strtolower(trim($hdr)), $this->addr_headers)) + $found = true; + } + } + if (!$found) + $test = $mod; + } + $this->form['tests'][$i]['type'] = $type; - $this->form['tests'][$i]['test'] = 'header'; + $this->form['tests'][$i]['test'] = $test; $this->form['tests'][$i]['arg1'] = $header; $this->form['tests'][$i]['arg2'] = $target; @@ -649,65 +746,20 @@ class managesieve extends rcube_plugin $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty'); else if (preg_match('/^(value|count)-/', $type) && !preg_match('/[0-9]+/', $target)) $this->errors['tests'][$i]['target'] = $this->gettext('forbiddenchars'); + + if ($mod) { + $this->form['tests'][$i]['part'] = $mod_type; + } } } - else - switch ($header) { - case 'size': - $sizeop = $this->strip_value($sizeops[$idx]); - $sizeitem = $this->strip_value($items[$idx]); - $sizetarget = $this->strip_value($sizetargets[$idx]); - - $this->form['tests'][$i]['test'] = 'size'; - $this->form['tests'][$i]['type'] = $sizeop; - $this->form['tests'][$i]['arg'] = $sizetarget.$sizeitem; - - if ($sizetarget == '') - $this->errors['tests'][$i]['sizetarget'] = $this->gettext('cannotbeempty'); - else if (!preg_match('/^[0-9]+(K|M|G)*$/i', $sizetarget)) - $this->errors['tests'][$i]['sizetarget'] = $this->gettext('forbiddenchars'); - break; - case '...': - $cust_header = $headers = $this->strip_value($cust_headers[$idx]); - - if (preg_match('/^not/', $op)) - $this->form['tests'][$i]['not'] = true; - $type = preg_replace('/^not/', '', $op); - - if ($cust_header == '') - $this->errors['tests'][$i]['header'] = $this->gettext('cannotbeempty'); - else { - $headers = preg_split('/[\s,]+/', $cust_header, -1, PREG_SPLIT_NO_EMPTY); - - if (!count($headers)) - $this->errors['tests'][$i]['header'] = $this->gettext('cannotbeempty'); - else { - foreach ($headers as $hr) - if (!preg_match('/^[a-z0-9-]+$/i', $hr)) - $this->errors['tests'][$i]['header'] = $this->gettext('forbiddenchars'); - } - } - if (empty($this->errors['tests'][$i]['header'])) - $cust_header = (is_array($headers) && count($headers) == 1) ? $headers[0] : $headers; + if ($header != 'size' && $comparator) { + if (preg_match('/^(value|count)/', $this->form['tests'][$i]['type'])) + $comparator = 'i;ascii-numeric'; + + $this->form['tests'][$i]['comparator'] = $comparator; + } - if ($type == 'exists') { - $this->form['tests'][$i]['test'] = 'exists'; - $this->form['tests'][$i]['arg'] = $cust_header; - } - else { - $this->form['tests'][$i]['test'] = 'header'; - $this->form['tests'][$i]['type'] = $type; - $this->form['tests'][$i]['arg1'] = $cust_header; - $this->form['tests'][$i]['arg2'] = $target; - - if ($target == '') - $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty'); - else if (preg_match('/^(value|count)-/', $type) && !preg_match('/[0-9]+/', $target)) - $this->errors['tests'][$i]['target'] = $this->gettext('forbiddenchars'); - } - break; - } $i++; } } @@ -1140,43 +1192,52 @@ class managesieve extends rcube_plugin $rule = isset($this->form) ? $this->form['tests'][$id] : $this->script[$fid]['tests'][$id]; $rows_num = isset($this->form) ? sizeof($this->form['tests']) : sizeof($this->script[$fid]['tests']); - $out = $div ? '<div class="rulerow" id="rulerow' .$id .'">'."\n" : ''; - - $out .= '<table><tr><td class="rowactions">'; - // headers select $select_header = new html_select(array('name' => "_header[]", 'id' => 'header'.$id, 'onchange' => 'rule_header_select(' .$id .')')); foreach($this->headers as $name => $val) $select_header->add(Q($this->gettext($name)), Q($val)); + if (in_array('body', $this->exts)) + $select_header->add(Q($this->gettext('body')), 'body'); $select_header->add(Q($this->gettext('size')), 'size'); $select_header->add(Q($this->gettext('...')), '...'); // TODO: list arguments + $aout = ''; - if ((isset($rule['test']) && $rule['test'] == 'header') - && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers)) - $out .= $select_header->show($rule['arg1']); + if ((isset($rule['test']) && in_array($rule['test'], array('header', 'address', 'envelope'))) + && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers) + ) { + $aout .= $select_header->show($rule['arg1']); + } else if ((isset($rule['test']) && $rule['test'] == 'exists') - && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers)) - $out .= $select_header->show($rule['arg']); + && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers) + ) { + $aout .= $select_header->show($rule['arg']); + } else if (isset($rule['test']) && $rule['test'] == 'size') - $out .= $select_header->show('size'); + $aout .= $select_header->show('size'); + else if (isset($rule['test']) && $rule['test'] == 'body') + $aout .= $select_header->show('body'); else if (isset($rule['test']) && $rule['test'] != 'true') - $out .= $select_header->show('...'); + $aout .= $select_header->show('...'); else - $out .= $select_header->show(); + $aout .= $select_header->show(); - $out .= '</td><td class="rowtargets">'; - - if ((isset($rule['test']) && $rule['test'] == 'header') - && (is_array($rule['arg1']) || !in_array($rule['arg1'], $this->headers))) - $custom = is_array($rule['arg1']) ? implode(', ', $rule['arg1']) : $rule['arg1']; - else if ((isset($rule['test']) && $rule['test'] == 'exists') - && (is_array($rule['arg']) || !in_array($rule['arg'], $this->headers))) - $custom = is_array($rule['arg']) ? implode(', ', $rule['arg']) : $rule['arg']; + if (isset($rule['test']) && in_array($rule['test'], array('header', 'address', 'envelope'))) { + if (is_array($rule['arg1'])) + $custom = implode(', ', $rule['arg1']); + else if (!in_array($rule['arg1'], $this->headers)) + $custom = $rule['arg1']; + } + else if (isset($rule['test']) && $rule['test'] == 'exists') { + if (is_array($rule['arg'])) + $custom = implode(', ', $rule['arg']); + else if (!in_array($rule['arg'], $this->headers)) + $custom = $rule['arg']; + } - $out .= '<div id="custom_header' .$id. '" style="display:' .(isset($custom) ? 'inline' : 'none'). '"> + $tout = '<div id="custom_header' .$id. '" style="display:' .(isset($custom) ? 'inline' : 'none'). '"> <input type="text" name="_custom_header[]" id="custom_header_i'.$id.'" ' . $this->error_class($id, 'test', 'header', 'custom_header_i') .' value="' .Q($custom). '" size="15" /> </div>' . "\n"; @@ -1215,33 +1276,43 @@ class managesieve extends rcube_plugin // target input (TODO: lists) - if ($rule['test'] == 'header') { - $out .= $select_op->show(($rule['not'] ? 'not' : '').$rule['type']); + if (in_array($rule['test'], array('header', 'address', 'envelope'))) { + $test = ($rule['not'] ? 'not' : '').($rule['type'] ? $rule['type'] : 'is'); $target = $rule['arg2']; } + else if ($rule['test'] == 'body') { + $test = ($rule['not'] ? 'not' : '').($rule['type'] ? $rule['type'] : 'is'); + $target = $rule['arg']; + } else if ($rule['test'] == 'size') { - $out .= $select_op->show(); - if (preg_match('/^([0-9]+)(K|M|G)*$/', $rule['arg'], $matches)) { + $test = ''; + $target = ''; + if (preg_match('/^([0-9]+)(K|M|G)?$/', $rule['arg'], $matches)) { $sizetarget = $matches[1]; $sizeitem = $matches[2]; } + else { + $sizetarget = $rule['arg']; + $sizeitem = $rule['item']; + } } else { - $out .= $select_op->show(($rule['not'] ? 'not' : '').$rule['test']); - $target = ''; + $test = ($rule['not'] ? 'not' : '').$rule['test']; + $target = ''; } - $out .= '<input type="text" name="_rule_target[]" id="rule_target' .$id. '" + $tout .= $select_op->show($test); + $tout .= '<input type="text" name="_rule_target[]" id="rule_target' .$id. '" value="' .Q($target). '" size="20" ' . $this->error_class($id, 'test', 'target', 'rule_target') . ' style="display:' . ($rule['test']!='size' && $rule['test'] != 'exists' ? 'inline' : 'none') . '" />'."\n"; $select_size_op = new html_select(array('name' => "_rule_size_op[]", 'id' => 'rule_size_op'.$id)); - $select_size_op->add(Q($this->gettext('filterunder')), 'under'); $select_size_op->add(Q($this->gettext('filterover')), 'over'); + $select_size_op->add(Q($this->gettext('filterunder')), 'under'); - $out .= '<div id="rule_size' .$id. '" style="display:' . ($rule['test']=='size' ? 'inline' : 'none') .'">'; - $out .= $select_size_op->show($rule['test']=='size' ? $rule['type'] : ''); - $out .= '<input type="text" name="_rule_size_target[]" id="rule_size_i'.$id.'" value="'.$sizetarget.'" size="10" ' + $tout .= '<div id="rule_size' .$id. '" style="display:' . ($rule['test']=='size' ? 'inline' : 'none') .'">'; + $tout .= $select_size_op->show($rule['test']=='size' ? $rule['type'] : ''); + $tout .= '<input type="text" name="_rule_size_target[]" id="rule_size_i'.$id.'" value="'.$sizetarget.'" size="10" ' . $this->error_class($id, 'test', 'sizetarget', 'rule_size_i') .' /> <input type="radio" name="_rule_size_item['.$id.']" value=""' . (!$sizeitem ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('B').' @@ -1251,7 +1322,82 @@ class managesieve extends rcube_plugin . ($sizeitem=='M' ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('MB').' <input type="radio" name="_rule_size_item['.$id.']" value="G"' . ($sizeitem=='G' ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('GB'); - $out .= '</div>'; + $tout .= '</div>'; + + // Advanced modifiers (address, envelope) + $select_mod = new html_select(array('name' => "_rule_mod[]", 'id' => 'rule_mod_op'.$id, + 'onchange' => 'rule_mod_select(' .$id .')')); + $select_mod->add(Q($this->gettext('none')), ''); + $select_mod->add(Q($this->gettext('address')), 'address'); + if (in_array('envelope', $this->exts)) + $select_mod->add(Q($this->gettext('envelope')), 'envelope'); + + $select_type = new html_select(array('name' => "_rule_mod_type[]", 'id' => 'rule_mod_type'.$id)); + $select_type->add(Q($this->gettext('allparts')), 'all'); + $select_type->add(Q($this->gettext('domain')), 'domain'); + $select_type->add(Q($this->gettext('localpart')), 'localpart'); + if (in_array('subaddress', $this->exts)) { + $select_type->add(Q($this->gettext('user')), 'user'); + $select_type->add(Q($this->gettext('detail')), 'detail'); + } + + $need_mod = $rule['test'] != 'size' && $rule['test'] != 'body'; + $mout = '<div id="rule_mod' .$id. '" class="adv" style="display:' . ($need_mod ? 'block' : 'none') .'">'; + $mout .= ' <span>'; + $mout .= Q($this->gettext('modifier')) . ' '; + $mout .= $select_mod->show($rule['test']); + $mout .= '</span>'; + $mout .= ' <span id="rule_mod_type' . $id . '"'; + $mout .= ' style="display:' . (in_array($rule['test'], array('address', 'envelope')) ? 'inline' : 'none') .'">'; + $mout .= Q($this->gettext('modtype')) . ' '; + $mout .= $select_type->show($rule['part']); + $mout .= '</span>'; + $mout .= '</div>'; + + // Advanced modifiers (body transformations) + $select_mod = new html_select(array('name' => "_rule_trans[]", 'id' => 'rule_trans_op'.$id, + 'onchange' => 'rule_trans_select(' .$id .')')); + $select_mod->add(Q($this->gettext('text')), 'text'); + $select_mod->add(Q($this->gettext('undecoded')), 'raw'); + $select_mod->add(Q($this->gettext('contenttype')), 'content'); + + $mout .= '<div id="rule_trans' .$id. '" class="adv" style="display:' . ($rule['test'] == 'body' ? 'block' : 'none') .'">'; + $mout .= ' <span>'; + $mout .= Q($this->gettext('modifier')) . ' '; + $mout .= $select_mod->show($rule['part']); + $mout .= '<input type="text" name="_rule_trans_type[]" id="rule_trans_type'.$id + . '" value="'.(is_array($rule['content']) ? implode(',', $rule['content']) : $rule['content']) + .'" size="20" style="display:' . ($rule['part'] == 'content' ? 'inline' : 'none') .'"' + . $this->error_class($id, 'test', 'part', 'rule_trans_type') .' />'; + $mout .= '</span>'; + $mout .= '</div>'; + + // Advanced modifiers (body transformations) + $select_comp = new html_select(array('name' => "_rule_comp[]", 'id' => 'rule_comp_op'.$id)); + $select_comp->add(Q($this->gettext('default')), ''); + $select_comp->add(Q($this->gettext('octet')), 'i;octet'); + $select_comp->add(Q($this->gettext('asciicasemap')), 'i;ascii-casemap'); + if (in_array('comparator-i;ascii-numeric', $this->exts)) { + $select_comp->add(Q($this->gettext('asciinumeric')), 'i;ascii-numeric'); + } + + $mout .= '<div id="rule_comp' .$id. '" class="adv" style="display:' . ($rule['test'] != 'size' ? 'block' : 'none') .'">'; + $mout .= ' <span>'; + $mout .= Q($this->gettext('comparator')) . ' '; + $mout .= $select_comp->show($rule['comparator']); + $mout .= '</span>'; + $mout .= '</div>'; + + // Build output table + $out = $div ? '<div class="rulerow" id="rulerow' .$id .'">'."\n" : ''; + $out .= '<table><tr>'; + $out .= '<td class="advbutton">'; + $out .= '<a href="#" id="ruleadv' . $id .'" title="'. Q($this->gettext('advancedopts')). '" + onclick="rule_adv_switch(' . $id .', this)" class="show"> </a>'; + $out .= '</td>'; + $out .= '<td class="rowactions">' . $aout . '</td>'; + $out .= '<td class="rowtargets">' . $tout . "\n"; + $out .= '<div id="rule_advanced' .$id. '" style="display:none">' . $mout . '</div>'; $out .= '</td>'; // add/del buttons @@ -1260,7 +1406,8 @@ class managesieve extends rcube_plugin onclick="rcmail.managesieve_ruleadd(' . $id .')" class="button add"></a>'; $out .= '<a href="#" id="ruledel' . $id .'" title="'. Q($this->gettext('del')). '" onclick="rcmail.managesieve_ruledel(' . $id .')" class="button del' . ($rows_num<2 ? ' disabled' : '') .'"></a>'; - $out .= '</td></tr></table>'; + $out .= '</td>'; + $out .= '</tr></table>'; $out .= $div ? "</div>\n" : ''; diff --git a/plugins/managesieve/package.xml b/plugins/managesieve/package.xml new file mode 100644 index 000000000..56655d248 --- /dev/null +++ b/plugins/managesieve/package.xml @@ -0,0 +1,100 @@ +<?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>managesieve</name> + <channel>pear.roundcube.net</channel> + <summary>Sieve filters manager for Roundcube</summary> + <description> + Adds a possibility to manage Sieve scripts (incoming mail filters). + It's clickable interface which operates on text scripts and communicates + with server using managesieve protocol. Adds Filters tab in Settings. + </description> + <lead> + <name>Aleksander Machniak</name> + <user>alec</user> + <email>alec@alec.pl</email> + <active>yes</active> + </lead> + <date>2011-11-17</date> + <version> + <release>5.0</release> + <api>5.0</api> + </version> + <stability> + <release>stable</release> + <api>stable</api> + </stability> + <license uri="http://www.gnu.org/licenses/gpl-2.0.html">GNU GPLv2</license> + <notes>-</notes> + <contents> + <dir baseinstalldir="/" name="/"> + <file name="managesieve.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="managesieve.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="localization/bg_BG.inc" role="data"></file> + <file name="localization/cs_CZ.inc" role="data"></file> + <file name="localization/de_CH.inc" role="data"></file> + <file name="localization/de_DE.inc" role="data"></file> + <file name="localization/el_GR.inc" role="data"></file> + <file name="localization/en_GB.inc" role="data"></file> + <file name="localization/en_US.inc" role="data"></file> + <file name="localization/es_AR.inc" role="data"></file> + <file name="localization/es_ES.inc" role="data"></file> + <file name="localization/et_EE.inc" role="data"></file> + <file name="localization/fi_FI.inc" role="data"></file> + <file name="localization/fr_FR.inc" role="data"></file> + <file name="localization/gl_ES.inc" role="data"></file> + <file name="localization/hr_HR.inc" role="data"></file> + <file name="localization/hu_HU.inc" role="data"></file> + <file name="localization/it_IT.inc" role="data"></file> + <file name="localization/ja_JP.inc" role="data"></file> + <file name="localization/lv_LV.inc" role="data"></file> + <file name="localization/nb_NO.inc" role="data"></file> + <file name="localization/nl_NL.inc" role="data"></file> + <file name="localization/pl_PL.inc" role="data"></file> + <file name="localization/pt_BR.inc" role="data"></file> + <file name="localization/pt_PT.inc" role="data"></file> + <file name="localization/ru_RU.inc" role="data"></file> + <file name="localization/sk_SK.inc" role="data"></file> + <file name="localization/sl_SI.inc" role="data"></file> + <file name="localization/sv_SE.inc" role="data"></file> + <file name="localization/uk_UA.inc" role="data"></file> + <file name="localization/zh_CN.inc" role="data"></file> + <file name="localization/zh_TW.inc" role="data"></file> + <file name="skins/default/managesieve.css" role="data"></file> + <file name="skins/default/managesieve_mail.css" role="data"></file> + <file name="skins/default/templates/filteredit.html" role="data"></file> + <file name="skins/default/templates/managesieve.html" role="data"></file> + <file name="skins/default/templates/setedit.html" role="data"></file> + <file name="skins/default/images/add.png" role="data"></file> + <file name="skins/default/images/del.png" role="data"></file> + <file name="skins/default/images/down_small.gif" role="data"></file> + <file name="skins/default/images/filter.png" role="data"></file> + <file name="skins/default/images/up_small.gif" role="data"></file> + <file name="managesieve.php" role="php"></file> + <file name="lib/rcube_sieve.php" role="php"></file> + <file name="lib/rcube_sieve_script.php" role="php"></file> + <file name="lib/Net/Sieve.php" role="php"></file> + <file name="config.inc.php.dist" role="data"></file> + </dir> + <!-- / --> + </contents> + <dependencies> + <required> + <php> + <min>5.2.1</min> + </php> + <pearinstaller> + <min>1.7.0</min> + </pearinstaller> + </required> + </dependencies> + <phprelease/> +</package> diff --git a/plugins/managesieve/skins/default/images/down_small.gif b/plugins/managesieve/skins/default/images/down_small.gif Binary files differnew file mode 100644 index 000000000..f865893f4 --- /dev/null +++ b/plugins/managesieve/skins/default/images/down_small.gif diff --git a/plugins/managesieve/skins/default/images/toolbar.png b/plugins/managesieve/skins/default/images/toolbar.png Binary files differdeleted file mode 100644 index 473dbc8df..000000000 --- a/plugins/managesieve/skins/default/images/toolbar.png +++ /dev/null diff --git a/plugins/managesieve/skins/default/images/up_small.gif b/plugins/managesieve/skins/default/images/up_small.gif Binary files differnew file mode 100644 index 000000000..40deb891f --- /dev/null +++ b/plugins/managesieve/skins/default/images/up_small.gif diff --git a/plugins/managesieve/skins/default/managesieve.css b/plugins/managesieve/skins/default/managesieve.css index cef26d4f3..0b82be6f3 100644 --- a/plugins/managesieve/skins/default/managesieve.css +++ b/plugins/managesieve/skins/default/managesieve.css @@ -149,6 +149,35 @@ div.rulerow table, div.actionrow table min-width: 620px; } +td +{ + vertical-align: top; +} + +td.advbutton +{ + width: 1%; +} + +td.advbutton a +{ + display: block; + padding-top: 14px; + height: 6px; + width: 12px; + text-decoration: none; +} + +td.advbutton a.show +{ + background: url(images/down_small.gif) center no-repeat; +} + +td.advbutton a.hide +{ + background: url(images/up_small.gif) center no-repeat; +} + td.rowbuttons { text-align: right; @@ -169,6 +198,11 @@ td.rowtargets padding-left: 3px; } +td.rowtargets div.adv +{ + padding-top: 3px; +} + input.disabled, input.disabled:hover { color: #999999; @@ -183,6 +217,7 @@ input.box, input.radio { border: 0; + margin-top: 0; } select.operator_selector @@ -190,6 +225,7 @@ select.operator_selector width: 200px; } +td.rowtargets span, span.label { color: #666666; @@ -243,7 +279,7 @@ a.button.add background: url(images/add.png) no-repeat; width: 30px; height: 20px; - margin-right: 4px; + margin-right: 4px; display: inline-block; } diff --git a/plugins/managesieve/tests/parser.phpt b/plugins/managesieve/tests/parser.phpt index d70353459..aec042187 100644 --- a/plugins/managesieve/tests/parser.phpt +++ b/plugins/managesieve/tests/parser.phpt @@ -6,7 +6,7 @@ Main test of script parser include '../lib/rcube_sieve_script.php'; $txt = ' -require ["fileinto","vacation","reject","relational","comparator-i;ascii-numeric","imapflags"]; +require ["fileinto","reject","envelope"]; # rule:[spam] if anyof (header :contains "X-DSPAM-Result" "Spam") { @@ -14,28 +14,17 @@ if anyof (header :contains "X-DSPAM-Result" "Spam") stop; } # rule:[test1] -if anyof (header :contains ["From","To"] "test@domain.tld") +if anyof (header :comparator "i;ascii-casemap" :contains ["From","To"] "test@domain.tld") { discard; stop; } # rule:[test2] -if anyof (not header :contains ["Subject"] "[test]", header :contains "Subject" "[test2]") +if anyof (not header :comparator "i;octet" :contains ["Subject"] "[test]", header :contains "Subject" "[test2]") { fileinto "test"; stop; } -# rule:[test-vacation] -if anyof (header :contains "Subject" "vacation") -{ - vacation :days 1 text: -# test -test test /* test */ -test -. -; - stop; -} # rule:[comments] if anyof (true) /* comment * "comment" #comment */ { @@ -44,24 +33,40 @@ if anyof (true) /* comment } # rule:[reject] if size :over 5000K { - reject "Message over 5MB size limit. Please contact me before sending this."; + reject "Message over 5MB size limit. Please contact me before sending this."; +} +# rule:[false] +if false # size :over 5000K +{ + stop; /* rule disabled */ +} +# rule:[true] +if true +{ + stop; } -# rule:[redirect] -if header :value "ge" :comparator "i;ascii-numeric" - ["X-Spam-score"] ["14"] {redirect "test@test.tld";} -# rule:[imapflags] -if header :matches "Subject" "^Test$" { - setflag "\\\\Seen"; - addflag ["\\\\Answered","\\\\Deleted"]; +fileinto "Test"; +# rule:[address test] +if address :all :is "From" "nagios@domain.tld" +{ + fileinto "domain.tld"; + stop; +} +# rule:[envelope test] +if envelope :domain :is "From" "domain.tld" +{ + fileinto "domain.tld"; + stop; } '; $s = new rcube_sieve_script($txt); echo $s->as_text(); +// ------------------------------------------------------------------------------- ?> --EXPECT-- -require ["fileinto","vacation","reject","relational","comparator-i;ascii-numeric","imapflags"]; +require ["fileinto","reject","envelope"]; # rule:[spam] if header :contains "X-DSPAM-Result" "Spam" { @@ -75,22 +80,11 @@ if header :contains ["From","To"] "test@domain.tld" stop; } # rule:[test2] -if anyof (not header :contains "Subject" "[test]", header :contains "Subject" "[test2]") +if anyof (not header :comparator "i;octet" :contains "Subject" "[test]", header :contains "Subject" "[test2]") { fileinto "test"; stop; } -# rule:[test-vacation] -if header :contains "Subject" "vacation" -{ - vacation :days 1 text: -# test -test test /* test */ -test -. -; - stop; -} # rule:[comments] if true { @@ -101,14 +95,26 @@ if size :over 5000K { reject "Message over 5MB size limit. Please contact me before sending this."; } -# rule:[redirect] -if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14" +# rule:[false] +if false # size :over 5000K { - redirect "test@test.tld"; + stop; } -# rule:[imapflags] -if header :matches "Subject" "^Test$" +# rule:[true] +if true { - setflag "\\Seen"; - addflag ["\\Answered","\\Deleted"]; + stop; +} +fileinto "Test"; +# rule:[address test] +if address :all :is "From" "nagios@domain.tld" +{ + fileinto "domain.tld"; + stop; +} +# rule:[envelope test] +if envelope :domain :is "From" "domain.tld" +{ + fileinto "domain.tld"; + stop; } diff --git a/plugins/managesieve/tests/parser_body.phpt b/plugins/managesieve/tests/parser_body.phpt new file mode 100644 index 000000000..08ad54959 --- /dev/null +++ b/plugins/managesieve/tests/parser_body.phpt @@ -0,0 +1,49 @@ +--TEST-- +Test of Sieve body extension (RFC5173) +--SKIPIF-- +--FILE-- +<?php +include '../lib/rcube_sieve_script.php'; + +$txt = ' +require ["body","fileinto"]; +if body :raw :contains "MAKE MONEY FAST" +{ + stop; +} +if body :content "text" :contains ["missile","coordinates"] +{ + fileinto "secrets"; +} +if body :content "audio/mp3" :contains "" +{ + fileinto "jukebox"; +} +if body :text :contains "project schedule" +{ + fileinto "project/schedule"; +} +'; + +$s = new rcube_sieve_script($txt); +echo $s->as_text(); + +?> +--EXPECT-- +require ["body","fileinto"]; +if body :raw :contains "MAKE MONEY FAST" +{ + stop; +} +if body :content "text" :contains ["missile","coordinates"] +{ + fileinto "secrets"; +} +if body :content "audio/mp3" :contains "" +{ + fileinto "jukebox"; +} +if body :text :contains "project schedule" +{ + fileinto "project/schedule"; +} diff --git a/plugins/managesieve/tests/parser_imapflags.phpt b/plugins/managesieve/tests/parser_imapflags.phpt new file mode 100644 index 000000000..a4bc465a3 --- /dev/null +++ b/plugins/managesieve/tests/parser_imapflags.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test of Sieve vacation extension (RFC5232) +--SKIPIF-- +--FILE-- +<?php +include '../lib/rcube_sieve_script.php'; + +$txt = ' +require ["imapflags"]; +# rule:[imapflags] +if header :matches "Subject" "^Test$" { + setflag "\\\\Seen"; + addflag ["\\\\Answered","\\\\Deleted"]; +} +'; + +$s = new rcube_sieve_script($txt, array('imapflags')); +echo $s->as_text(); + +?> +--EXPECT-- +require ["imapflags"]; +# rule:[imapflags] +if header :matches "Subject" "^Test$" +{ + setflag "\\Seen"; + addflag ["\\Answered","\\Deleted"]; +} diff --git a/plugins/managesieve/tests/parser_include.phpt b/plugins/managesieve/tests/parser_include.phpt new file mode 100644 index 000000000..addc0d449 --- /dev/null +++ b/plugins/managesieve/tests/parser_include.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test of Sieve include extension +--SKIPIF-- +--FILE-- +<?php +include '../lib/rcube_sieve_script.php'; + +$txt = ' +require ["include"]; + +include "script.sieve"; +# rule:[two] +if true +{ + include :optional "second.sieve"; +} +'; + +$s = new rcube_sieve_script($txt, array(), array('variables')); +echo $s->as_text(); + +?> +--EXPECT-- +require ["include"]; +include "script.sieve"; +# rule:[two] +if true +{ + include :optional "second.sieve"; +} diff --git a/plugins/managesieve/tests/parser_kep14.phpt b/plugins/managesieve/tests/parser_kep14.phpt index 06beaeda1..dcdbd48a0 100644 --- a/plugins/managesieve/tests/parser_kep14.phpt +++ b/plugins/managesieve/tests/parser_kep14.phpt @@ -10,7 +10,7 @@ $txt = ' # EDITOR_VERSION 123 '; -$s = new rcube_sieve_script($txt, array()); +$s = new rcube_sieve_script($txt, array('body')); echo $s->as_text(); ?> diff --git a/plugins/managesieve/tests/parser_prefix.phpt b/plugins/managesieve/tests/parser_prefix.phpt new file mode 100644 index 000000000..c87e9658f --- /dev/null +++ b/plugins/managesieve/tests/parser_prefix.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test of prefix comments handling +--SKIPIF-- +--FILE-- +<?php +include '../lib/rcube_sieve_script.php'; + +$txt = ' +# this is a comment +# and the second line + +require ["variables"]; +set "b" "c"; +'; + +$s = new rcube_sieve_script($txt, array(), array('variables')); +echo $s->as_text(); + +?> +--EXPECT-- +# this is a comment +# and the second line + +require ["variables"]; +set "b" "c"; diff --git a/plugins/managesieve/tests/parser_relational.phpt b/plugins/managesieve/tests/parser_relational.phpt new file mode 100644 index 000000000..6b6f29f4c --- /dev/null +++ b/plugins/managesieve/tests/parser_relational.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test of Sieve relational extension (RFC5231) +--SKIPIF-- +--FILE-- +<?php +include '../lib/rcube_sieve_script.php'; + +$txt = ' +require ["relational","comparator-i;ascii-numeric"]; +# rule:[redirect] +if header :value "ge" :comparator "i;ascii-numeric" + ["X-Spam-score"] ["14"] {redirect "test@test.tld";} +'; + +$s = new rcube_sieve_script($txt); +echo $s->as_text(); + +?> +--EXPECT-- +require ["relational","comparator-i;ascii-numeric"]; +# rule:[redirect] +if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14" +{ + redirect "test@test.tld"; +} diff --git a/plugins/managesieve/tests/parser_vacation.phpt b/plugins/managesieve/tests/parser_vacation.phpt new file mode 100644 index 000000000..a603ff6c1 --- /dev/null +++ b/plugins/managesieve/tests/parser_vacation.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test of Sieve vacation extension (RFC5230) +--SKIPIF-- +--FILE-- +<?php +include '../lib/rcube_sieve_script.php'; + +$txt = ' +require ["vacation"]; +# rule:[test-vacation] +if anyof (header :contains "Subject" "vacation") +{ + vacation :days 1 text: +# test +test test /* test */ +test +. +; + stop; +} +'; + +$s = new rcube_sieve_script($txt); +echo $s->as_text(); + +?> +--EXPECT-- +require ["vacation"]; +# rule:[test-vacation] +if header :contains "Subject" "vacation" +{ + vacation :days 1 text: +# test +test test /* test */ +test +. +; + stop; +} diff --git a/plugins/managesieve/tests/parser_variables.phpt b/plugins/managesieve/tests/parser_variables.phpt new file mode 100644 index 000000000..cf1f8fcad --- /dev/null +++ b/plugins/managesieve/tests/parser_variables.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test of Sieve variables extension +--SKIPIF-- +--FILE-- +<?php +include '../lib/rcube_sieve_script.php'; + +$txt = ' +require ["variables"]; +set "honorific" "Mr"; +set "vacation" text: +Dear ${HONORIFIC} ${last_name}, +I am out, please leave a message after the meep. +. +; +set :length "b" "${a}"; +set :lower "b" "${a}"; +set :upperfirst "b" "${a}"; +set :upperfirst :lower "b" "${a}"; +set :quotewildcard "b" "Rock*"; +'; + +$s = new rcube_sieve_script($txt, array(), array('variables')); +echo $s->as_text(); + +?> +--EXPECT-- +require ["variables"]; +set "honorific" "Mr"; +set "vacation" text: +Dear ${HONORIFIC} ${last_name}, +I am out, please leave a message after the meep. +. +; +set :length "b" "${a}"; +set :lower "b" "${a}"; +set :upperfirst "b" "${a}"; +set :upperfirst :lower "b" "${a}"; +set :quotewildcard "b" "Rock*"; diff --git a/plugins/managesieve/tests/parset_subaddress.phpt b/plugins/managesieve/tests/parset_subaddress.phpt new file mode 100644 index 000000000..6d4d03c6e --- /dev/null +++ b/plugins/managesieve/tests/parset_subaddress.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test of Sieve subaddress extension (RFC5233) +--SKIPIF-- +--FILE-- +<?php +include '../lib/rcube_sieve_script.php'; + +$txt = ' +require ["envelope","subaddress","fileinto"]; +if envelope :user "To" "postmaster" +{ + fileinto "postmaster"; + stop; +} +if envelope :detail :is "To" "mta-filters" +{ + fileinto "mta-filters"; + stop; +} +'; + +$s = new rcube_sieve_script($txt); +echo $s->as_text(); + +// ------------------------------------------------------------------------------- +?> +--EXPECT-- +require ["envelope","subaddress","fileinto"]; +if envelope :user "To" "postmaster" +{ + fileinto "postmaster"; + stop; +} +if envelope :detail :is "To" "mta-filters" +{ + fileinto "mta-filters"; + stop; +} diff --git a/plugins/newmail_notifier/newmail_notifier.php b/plugins/newmail_notifier/newmail_notifier.php index 1f1df9e75..01e25984d 100644 --- a/plugins/newmail_notifier/newmail_notifier.php +++ b/plugins/newmail_notifier/newmail_notifier.php @@ -132,7 +132,7 @@ class newmail_notifier extends rcube_plugin */ function notify($args) { - if ($this->notified) { + if ($this->notified || !empty($_GET['_refresh'])) { return $args; } diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 002730f4b..a0a5f8189 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -3178,7 +3178,7 @@ class rcube_imap if (is_array($ns)) { foreach ($ns as $ns_data) { if (strlen($ns_data[0])) { - $search = $ns_data[0]; + $search[] = $ns_data[0]; } } } diff --git a/program/js/app.js b/program/js/app.js index 5902b1d74..8b98a4268 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -5738,7 +5738,7 @@ function rcube_webmail() this.plain2html = function(plainText, id) { var lock = this.set_busy(true, 'converting'); - $(document.getElementById(id)).val('<pre>'+plainText+'</pre>'); + $(document.getElementById(id)).val(plainText ? '<pre>'+plainText+'</pre>' : ''); this.set_busy(false, null, lock); }; diff --git a/program/js/googiespell.js b/program/js/googiespell.js index e3fdf7d3b..96d612ca2 100644 --- a/program/js/googiespell.js +++ b/program/js/googiespell.js @@ -1,6 +1,8 @@ /* SpellCheck jQuery'fied spell checker based on GoogieSpell 4.0 + (which was published under GPL "version 2 or any later version") + Copyright (C) 2006 Amir Salihefendic Copyright (C) 2009 Aleksander Machniak Copyright (C) 2011 Kolab Systems AG |