From 59478e06c25303a790a0840ab2ac30662c4ef781 Mon Sep 17 00:00:00 2001 From: Hugues Hiegel Date: Tue, 5 Aug 2014 16:46:22 +0200 Subject: c'est la merde.. --- plugins/sieverules/include/rcube_sieve_script.php | 969 ++++++++++++++++++++++ 1 file changed, 969 insertions(+) create mode 100644 plugins/sieverules/include/rcube_sieve_script.php (limited to 'plugins/sieverules/include/rcube_sieve_script.php') diff --git a/plugins/sieverules/include/rcube_sieve_script.php b/plugins/sieverules/include/rcube_sieve_script.php new file mode 100644 index 000000000..2dbd52a28 --- /dev/null +++ b/plugins/sieverules/include/rcube_sieve_script.php @@ -0,0 +1,969 @@ + | + | Modifications by: Philip Weir | + | * Changed name of keys in script array | + | * Added support for address and envelope | + | * Added support for vacation | + | * Added support for disabled rules (written to file as comment) | + | * Added support for regex tests | + | * Added support for imapflags | + | * Added support for relational operators and comparators | + | * Added support for subaddress tests | + | * Added support for notify action | + | * Added support for stop action | + | * Added support for body and copy | + | * Added support for spamtest and virustest | + | * Added support for date | + | * Added support for editheader | + +-----------------------------------------------------------------------+ +*/ + +define('SIEVE_ERROR_BAD_ACTION', 1); +define('SIEVE_ERROR_NOT_FOUND', 2); + +// define constants for sieve file +if (!defined('RCUBE_SIEVE_NEWLINE')) + define('RCUBE_SIEVE_NEWLINE', "\r\n"); + +if (!defined('RCUBE_SIEVE_INDENT')) + define('RCUBE_SIEVE_INDENT', "\t"); + +if (!defined('RCUBE_SIEVE_HEADER')) + define('RCUBE_SIEVE_HEADER', "## Generated by Roundcube Webmail SieveRules Plugin ##"); + +class rcube_sieve_script +{ + private $elsif = true; + private $content = array(); + private $supported = array( + 'fileinto', + 'reject', + 'ereject', + 'vacation', + 'imapflags', + 'imap4flags', + 'notify', + 'enotify', + 'spamtest', + 'virustest', + 'date', + 'editheader' + ); + public $raw = ''; + + public function __construct($script, $ext = array(), $elsif = true) + { + $this->raw = $script; + $this->elsif = $elsif; + + // adjust supported extenstion to match sieve server + $this->supported = array_intersect($this->supported, $ext); + if (in_array('copy', $ext)) + $this->supported = array_merge($this->supported, array('fileinto_copy','redirect_copy')); + + if (in_array('editheader', $ext)) + $this->supported = array_merge($this->supported, array('editheaderadd','editheaderrem')); + + // include standard actions in supported list + $this->supported = array_merge($this->supported, array('redirect','keep','discard','stop')); + + // load script + $this->content = $this->parse_text($script); + } + + public function add_text($script) + { + $content = $this->parse_text($script); + $result = false; + + // check existsing script rules names + foreach ($this->content as $idx => $elem) + $names[$elem['name']] = $idx; + + foreach ($content as $elem) { + if (!isset($names[$elem['name']])) { + array_push($this->content, $elem); + $result = true; + } + } + + return $result; + } + + public function import_filters($content) + { + if (is_array($content)) { + $result = false; + + // check existsing script rules names + foreach ($this->content as $idx => $elem) + $names[$elem['name']] = $idx; + + foreach ($content as $elem) { + if (!isset($names[$elem['name']])) { + array_push($this->content, $elem); + $result = true; + } + } + } + else { + $this->add_text($content); + } + } + + public function add_rule($content, $pos = null) + { + foreach ($content['actions'] as $action) { + if (!in_array($action['type'], $this->supported)) + return SIEVE_ERROR_BAD_ACTION; + } + + if ($pos !== null) + array_splice($this->content, $pos, 0, array($content)); + else + array_push($this->content, $content); + + return true; + } + + public function delete_rule($index) + { + if (isset($this->content[$index])) { + unset($this->content[$index]); + $this->content = array_values($this->content); + return true; + } + + return SIEVE_ERROR_NOT_FOUND; + } + + public function size() + { + return sizeof($this->content); + } + + public function update_rule($index, $content) + { + foreach ($content['actions'] as $action) { + if (!in_array($action['type'], $this->supported)) + return SIEVE_ERROR_BAD_ACTION; + } + + if ($this->content[$index]) { + $this->content[$index] = $content; + return true; + } + + return SIEVE_ERROR_NOT_FOUND; + } + + public function move_rule($source, $destination) + { + $this->add_rule($this->content[$source], $destination); + + if ($source < $destination) + $this->delete_rule($source); + else + $this->delete_rule($source + 1); + } + + public function as_text() + { + $script = ''; + $variables = ''; + $exts = array(); + + // rules + $activeRules = 0; + foreach ($this->content as $rule) { + $tests = array(); + $i = 0; + + if ($rule['disabled'] == 1) { + $script .= '# rule:[' . $rule['name'] . "]" . RCUBE_SIEVE_NEWLINE; + $script .= '# disabledRule:[' . $this->_safe_serial(serialize($rule)) . "]" . RCUBE_SIEVE_NEWLINE; + } + else { + // header + $script .= '# rule:[' . $rule['name'] . "]" . RCUBE_SIEVE_NEWLINE; + + // constraints expressions + foreach ($rule['tests'] as $test) { + $tests[$i] = ''; + + switch ($test['type']) { + case 'size': + $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= 'size :' . ($test['operator']=='under' ? 'under ' : 'over ') . $test['target']; + break; + case 'virustest': + case 'spamtest': + array_push($exts, $test['type']); + array_push($exts, 'relational'); + array_push($exts, 'comparator-i;ascii-numeric'); + $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= $test['type'] . ' :value ' . ($test['operator'] == 'eq' ? '"eq" ' : + ($test['operator'] == 'le' ? '"le" ' : '"ge" ')) . + ':comparator "i;ascii-numeric" "' . $test['target'] .'"'; + break; + case 'true': + $tests[$i] .= ($test['not'] ? 'not true' : 'true'); + break; + case 'exists': + $tests[$i] .= ($test['not'] ? 'not ' : ''); + + if (is_array($test['header'])) + $tests[$i] .= 'exists ["' . implode('", "', $this->_escape_string($test['header'])) . '"]'; + else + $tests[$i] .= 'exists "' . $this->_escape_string($test['header']) . '"'; + + break; + case 'envelope': + array_push($exts, 'envelope'); + case 'header': + case 'address': + if ($test['operator'] == 'regex') + array_push($exts, 'regex'); + elseif (substr($test['operator'], 0, 5) == 'count' || substr($test['operator'], 0, 5) == 'value') + array_push($exts, 'relational'); + elseif ($test['operator'] == 'user' || $test['operator'] == 'detail' || $test['operator'] == 'domain') + array_push($exts, 'subaddress'); + + $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= $test['type']. ' :' . $test['operator']; + + if ($test['comparator'] != '') { + if ($test['comparator'] != 'i;ascii-casemap' && $test['comparator'] != 'i;octet') + array_push($exts, 'comparator-' . $test['comparator']); + + $tests[$i] .= ' :comparator "' . $test['comparator'] . '"'; + } + + if (is_array($test['header'])) + $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['header'])) . '"]'; + else + $tests[$i] .= ' "' . $this->_escape_string($test['header']) . '"'; + + if (is_array($test['target'])) + $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['target'])) . '"]'; + else + $tests[$i] .= ' "' . $this->_escape_string($test['target']) . '"'; + + break; + case 'body': + array_push($exts, 'body'); + if ($test['operator'] == 'regex') + array_push($exts, 'regex'); + elseif (substr($test['operator'], 0, 5) == 'count' || substr($test['operator'], 0, 5) == 'value') + array_push($exts, 'relational'); + + $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= $test['type']; + + if ($test['bodypart'] != '') + $tests[$i] .= ' :' . $test['bodypart']; + + if ($test['contentpart'] != '') + $tests[$i] .= ' "'. $test['contentpart'] .'"'; + + $tests[$i] .= ' :' . $test['operator']; + + if ($test['comparator'] != '') { + if ($test['comparator'] != 'i;ascii-casemap' && $test['comparator'] != 'i;octet') + array_push($exts, 'comparator-' . $test['comparator']); + + $tests[$i] .= ' :comparator "' . $test['comparator'] . '"'; + } + + if (is_array($test['target'])) + $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['target'])) . '"]'; + else + $tests[$i] .= ' "' . $this->_escape_string($test['target']) . '"'; + + break; + case 'date': + array_push($exts, 'date'); + if ($test['operator'] == 'regex') + array_push($exts, 'regex'); + elseif (substr($test['operator'], 0, 5) == 'count' || substr($test['operator'], 0, 5) == 'value') + array_push($exts, 'relational'); + + $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= $test['header']; + + $timezone = rcube::get_instance()->config->get('timezone', 'auto'); + if ($timezone != 'auto') { + $tz = new DateTimeZone($timezone); + $date = new DateTime('now', $tz); + $tests[$i] .= ' :zone ' . '"' . $date->format('O') . '"'; + } + + $tests[$i] .= ' :' . $test['operator']; + + if ($test['comparator'] != '') { + if ($test['comparator'] != 'i;ascii-casemap' && $test['comparator'] != 'i;octet') + array_push($exts, 'comparator-' . $test['comparator']); + + $tests[$i] .= ' :comparator "' . $test['comparator'] . '"'; + } + + $tests[$i] .= ' "' . $this->_escape_string($test['datepart']) . '"'; + $tests[$i] .= ' "' . $this->_escape_string($test['target']) . '"'; + + break; + } + + $i++; + } + + $script .= ($activeRules > 0 && $this->elsif ? 'els' : '') . ($rule['join'] ? 'if allof (' : 'if anyof ('); + $activeRules++; + + if (sizeof($tests) > 1) + $script .= implode("," . RCUBE_SIEVE_NEWLINE . RCUBE_SIEVE_INDENT, $tests); + elseif (sizeof($tests)) + $script .= $tests[0]; + else + $script .= 'true'; + + + $script .= ")". RCUBE_SIEVE_NEWLINE ."{" . RCUBE_SIEVE_NEWLINE; + + // action(s) + $actions = ''; + foreach ($rule['actions'] as $action) { + switch ($action['type']) { + case 'fileinto': + case 'fileinto_copy': + array_push($exts, 'fileinto'); + + $args = ''; + if ($action['type'] == 'fileinto_copy') { + array_push($exts, 'copy'); + $args .= ' :copy'; + } + + if ($action['create']) { + array_push($exts, 'mailbox'); + $args .= ' :create'; + } + + // variables support in fileinto by David Warden + if (preg_match('/\$\{\d+\}/', $action['target'])) + array_push($exts, 'variables'); + + $actions .= RCUBE_SIEVE_INDENT . "fileinto" . $args . " \"" . $this->_escape_string($action['target']) . "\";" . RCUBE_SIEVE_NEWLINE; + break; + case 'redirect': + case 'redirect_copy': + $args = ''; + if ($action['type'] == 'redirect_copy') { + array_push($exts, 'copy'); + $args .= ' :copy'; + } + + $tokens = preg_split('/[,;\s]/', $action['target']); + foreach ($tokens as $email) + $actions .= RCUBE_SIEVE_INDENT . "redirect" . $args . " \"" . $this->_escape_string($email) . "\";" . RCUBE_SIEVE_NEWLINE; + break; + case 'reject': + case 'ereject': + array_push($exts, $action['type']); + + if (strpos($action['target'], "\n")!==false) + $actions .= RCUBE_SIEVE_INDENT . $action['type']." text:" . RCUBE_SIEVE_NEWLINE . $action['target'] . RCUBE_SIEVE_NEWLINE . "." . RCUBE_SIEVE_NEWLINE . ";" . RCUBE_SIEVE_NEWLINE; + else + $actions .= RCUBE_SIEVE_INDENT . $action['type']." \"" . $this->_escape_string($action['target']) . "\";" . RCUBE_SIEVE_NEWLINE; + + break; + case 'vacation': + array_push($exts, 'vacation'); + $action['subject'] = $this->_escape_string($action['subject']); + +// // encoding subject header with mb_encode provides better results with asian characters +// if (function_exists("mb_encode_mimeheader")) +// { +// mb_internal_encoding($action['charset']); +// $action['subject'] = mb_encode_mimeheader($action['subject'], $action['charset'], 'Q'); +// mb_internal_encoding(RCUBE_CHARSET); +// } + + // detect original recipient + if ($action['from'] == 'auto' && strpos($variables, 'set "from"') === false) { + array_push($exts, 'variables'); + + $variables .= 'set "from" "";' . RCUBE_SIEVE_NEWLINE; + $variables .= 'if header :matches "to" "*" {' . RCUBE_SIEVE_NEWLINE; + $variables .= RCUBE_SIEVE_INDENT . 'set "from" "${1}";' . RCUBE_SIEVE_NEWLINE; + $variables .= '}' . RCUBE_SIEVE_NEWLINE; + + $action['from'] = "\${from}"; + } + elseif ($action['from'] == 'auto') { + $action['from'] = "\${from}"; + } + + // append original subject + if ($action['origsubject'] == '1' && strpos($variables, 'set "subject"') === false) { + array_push($exts, 'variables'); + + $variables .= 'set "subject" "";' . RCUBE_SIEVE_NEWLINE; + $variables .= 'if header :matches "subject" "*" {' . RCUBE_SIEVE_NEWLINE; + $variables .= RCUBE_SIEVE_INDENT . 'set "subject" "${1}";' . RCUBE_SIEVE_NEWLINE; + $variables .= '}' . RCUBE_SIEVE_NEWLINE; + + $action['subject'] = trim($action['subject']); + if (substr($action['subject'], -1, 1) != ":") $action['subject'] .= ":"; + $action['subject'] .= " \${subject}"; + } + + $actions .= RCUBE_SIEVE_INDENT . "vacation" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['days'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":days ". $action['days'] . RCUBE_SIEVE_NEWLINE; + if (!empty($action['addresses'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":addresses [\"". str_replace(",", "\",\"", $this->_escape_string($action['addresses'])) ."\"]" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['subject'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":subject \"". $action['subject'] ."\"" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['handle'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":handle \"". $this->_escape_string($action['handle']) ."\"" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['from'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":from \"". $this->_escape_string($action['from']) ."\"" . RCUBE_SIEVE_NEWLINE; + + if ($action['htmlmsg']) { + $MAIL_MIME = new Mail_mime("\r\n"); + + $action['msg'] = '' . + "\r\n\r\n" . $action['msg'] . "\r\n\r\n"; + + $MAIL_MIME->setHTMLBody($action['msg']); + + // add a plain text version of the e-mail as an alternative part. + $h2t = new rcube_html2text($action['msg'], false, true, 0); + $plainTextPart = $h2t->get_text(); + if (!$plainTextPart) { + // empty message body breaks attachment handling in drafts + $plainTextPart = "\r\n"; + } + else { + // make sure all line endings are CRLF (#1486712) + $plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart); + } + + $MAIL_MIME->setTXTBody($plainTextPart); + + $MAIL_MIME->setParam('html_charset', $action['charset']); + $MAIL_MIME->setParam('text_charset', $action['charset']); + + $action['msg'] = $MAIL_MIME->getMessage(); + } + + // escape lines which start is a . + $action['msg'] = preg_replace('/(^|\r?\n)\./', "$1..", $action['msg']); + + if ($action['htmlmsg']) + $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":mime text:". RCUBE_SIEVE_NEWLINE . $action['msg'] . RCUBE_SIEVE_NEWLINE . "." . RCUBE_SIEVE_NEWLINE . ";" . RCUBE_SIEVE_NEWLINE; + elseif ($action['charset'] != "UTF-8") + $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":mime text:". RCUBE_SIEVE_NEWLINE ."Content-Type: text/plain; charset=". $action['charset'] . RCUBE_SIEVE_NEWLINE . RCUBE_SIEVE_NEWLINE . $action['msg'] . RCUBE_SIEVE_NEWLINE . "." . RCUBE_SIEVE_NEWLINE . ";" . RCUBE_SIEVE_NEWLINE; + elseif (strpos($action['msg'], "\n") !== false) + $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . "text:" . RCUBE_SIEVE_NEWLINE . $action['msg'] . RCUBE_SIEVE_NEWLINE . "." . RCUBE_SIEVE_NEWLINE . ";" . RCUBE_SIEVE_NEWLINE; + else + $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . "\"" . $this->_escape_string($action['msg']) . "\";" . RCUBE_SIEVE_NEWLINE; + + break; + case 'imapflags': + case 'imap4flags': + array_push($exts, $action['type']); + + if (strpos($actions, "setflag") !== false) + $actions .= RCUBE_SIEVE_INDENT . "addflag \"" . $this->_escape_string($action['target']) . "\";" . RCUBE_SIEVE_NEWLINE; + else + $actions .= RCUBE_SIEVE_INDENT . "setflag \"" . $this->_escape_string($action['target']) . "\";" . RCUBE_SIEVE_NEWLINE; + + break; + case 'notify': + array_push($exts, 'notify'); + $actions .= RCUBE_SIEVE_INDENT . "notify" . RCUBE_SIEVE_NEWLINE; + $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":method \"" . $this->_escape_string($action['method']) . "\"" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['options'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":options [\"" . str_replace(",", "\",\"", $this->_escape_string($action['options'])) . "\"]" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['from'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":from \"" . $this->_escape_string($action['from']) . "\"" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['importance'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":importance \"" . $this->_escape_string($action['importance']) . "\"" . RCUBE_SIEVE_NEWLINE; + $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":message \"". $this->_escape_string($action['msg']) ."\";" . RCUBE_SIEVE_NEWLINE; + break; + case 'enotify': + array_push($exts, 'enotify'); + $actions .= RCUBE_SIEVE_INDENT . "notify" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['options'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":options [\"" . str_replace(",", "\",\"", $this->_escape_string($action['options'])) . "\"]" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['from'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":from \"" . $this->_escape_string($action['from']) . "\"" . RCUBE_SIEVE_NEWLINE; + if (!empty($action['importance'])) $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":importance \"" . $this->_escape_string($action['importance']) . "\"" . RCUBE_SIEVE_NEWLINE; + $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . ":message \"". $this->_escape_string($action['msg']) ."\"" . RCUBE_SIEVE_NEWLINE; + $actions .= RCUBE_SIEVE_INDENT . RCUBE_SIEVE_INDENT . "\"" . $this->_escape_string($action['method']) . "\";" . RCUBE_SIEVE_NEWLINE; + break; + case 'editheaderadd': + array_push($exts, 'editheader'); + $actions .= RCUBE_SIEVE_INDENT . "addheader"; + + if ($action['index'] == 'last') + $actions .= " :last"; + + $actions .= " \"". $this->_escape_string($action['name']) ."\" \"". $this->_escape_string($action['value']) ."\";" . RCUBE_SIEVE_NEWLINE; + break; + case 'editheaderrem': + array_push($exts, 'editheader'); + $actions .= RCUBE_SIEVE_INDENT . "deleteheader"; + + if (is_numeric($action['index'])) + $actions .= " :index " . $action['index']; + elseif ($action['index'] == 'last') + $actions .= " :last"; + + if (strlen($action['operator']) > 0) + $actions .= " :" . $action['operator']; + + $actions .= " \"". $this->_escape_string($action['name']) ."\""; + + if (strlen($action['value']) > 0) + $actions .= " \"". $this->_escape_string($action['value']) ."\""; + + $actions .= ";" . RCUBE_SIEVE_NEWLINE; + break; + case 'keep': + case 'discard': + case 'stop': + $actions .= RCUBE_SIEVE_INDENT . $action['type'] .";" . RCUBE_SIEVE_NEWLINE; + break; + } + } + + $script .= $actions . "}" . RCUBE_SIEVE_NEWLINE; + } + } + + if ($variables) + $variables .= RCUBE_SIEVE_NEWLINE; + + // requires + $exts = array_unique($exts); + if (sizeof($exts)) + $script = 'require ["' . implode('","', $exts) . "\"];" . RCUBE_SIEVE_NEWLINE . RCUBE_SIEVE_NEWLINE . $variables . $script; + + // author + if ($script && RCUBE_SIEVE_HEADER) + $script = RCUBE_SIEVE_HEADER . RCUBE_SIEVE_NEWLINE . $script; + + return $script; + } + + public function as_array() + { + return $this->content; + } + + public function parse_text($script) + { + $i = 0; + $content = array(); + + // remove C comments + $script = preg_replace('|/\*.*?\*/|sm', '', $script); + + // tokenize rules - \r is optional for backward compatibility (added 20090413) + if ($tokens = preg_split('/(# rule:\[.*\])\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) { + foreach($tokens as $token) { + if (preg_match('/^# rule:\[(.*)\]/', $token, $matches)) { + $content[$i]['name'] = $matches[1]; + } + elseif (isset($content[$i]['name']) && sizeof($content[$i]) == 1 && preg_match('/^# disabledRule:\[(.*)\]/', $token, $matches)) { + $content[$i] = unserialize($this->_regular_serial($matches[1])); + $i++; + } + elseif (isset($content[$i]['name']) && sizeof($content[$i]) == 1) { + if ($rule = $this->_tokenize_rule($token)) { + $content[$i] = array_merge($content[$i], $rule); + $i++; + } + else { + unset($content[$i]); + } + } + } + } + + return $content; + } + + private function _tokenize_rule($content) + { + $result = NULL; + + if (preg_match('/^(if|elsif|else)\s+((true|not\s+true|allof|anyof|exists|header|not|size|envelope|address|spamtest|virustest|date|currentdate)\s+(.*))\s+\{(.*)\}$/sm', trim($content), $matches)) { + list($tests, $join) = $this->_parse_tests(trim($matches[2])); + $actions = $this->_parse_actions(trim($matches[5])); + + if ($tests && $actions) { + $result = array( + 'tests' => $tests, + 'actions' => $actions, + 'join' => $join, + ); + } + } + + return $result; + } + + private function _parse_actions($content) + { + $content = str_replace("\r\n", "\n", $content); + $result = NULL; + + // supported actions + $patterns[] = '^\s*discard;'; + $patterns[] = '^\s*keep;'; + $patterns[] = '^\s*stop;'; + $patterns[] = '^\s*fileinto\s+(:copy\s+)?(:create\s+)?(.*?[^\\\]);'; + $patterns[] = '^\s*redirect\s+(:copy\s+)?(.*?[^\\\]);'; + $patterns[] = '^\s*setflag\s+(.*?[^\\\]);'; + $patterns[] = '^\s*addflag\s+(.*?[^\\\]);'; + $patterns[] = '^\s*reject\s+text:(.*)\n\.\n;'; + $patterns[] = '^\s*ereject\s+text:(.*)\n\.\n;'; + $patterns[] = '^\s*reject\s+(.*?[^\\\]);'; + $patterns[] = '^\s*ereject\s+(.*?[^\\\]);'; + $patterns[] = '^\s*vacation\s+(:days\s+([0-9]+)\s+)?(:addresses\s+\[(.*?[^\\\])\]\s+)?(:subject\s+(".*?[^"\\\]")\s+)?(:handle\s+(".*?[^"\\\]")\s+)?(:from\s+(".*?[^"\\\]")\s+)?(:mime\s+)?text:(.*)\n\.\n;'; + $patterns[] = '^\s*vacation\s+(:days\s+([0-9]+)\s+)?(:addresses\s+\[(.*?[^\\\])\]\s+)?(:subject\s+(".*?[^"\\\]")\s+)?(:handle\s+(".*?[^"\\\]")\s+)?(:from\s+(".*?[^"\\\]")\s+)?(.*?[^\\\]);'; + $patterns[] = '^\s*notify\s+:method\s+(".*?[^"\\\]")\s+(:options\s+\[(.*?[^\\\])\]\s+)?(:from\s+(".*?[^"\\\]")\s+)?(:importance\s+(".*?[^"\\\]")\s+)?:message\s+(".*?[^"\\\]");'; + $patterns[] = '^\s*notify\s+(:options\s+\[(.*?[^\\\])\]\s+)?(:from\s+(".*?[^"\\\]")\s+)?(:importance\s+(".*?[^"\\\]")\s+)?:message\s+(".*?[^"\\\]")\s+(.*);'; + $patterns[] = '^\s*addheader\s+(:(last))?\s*(".*?[^"\\\]")\s+(".*?[^"\\\]");'; + $patterns[] = '^\s*deleteheader\s+(:(last)|:index\s([0-9])+)?\s*(:(contains))?\s*(".*?[^"\\\]")\s*(".*?[^"\\\]")?;'; + + $pattern = '/(' . implode('$)|(', $patterns) . '$)/ms'; + + // parse actions body + if (preg_match_all($pattern, $content, $mm, PREG_SET_ORDER)) { + foreach ($mm as $m) { + $content = trim($m[0]); + + if (preg_match('/^(discard|keep|stop)/', $content, $matches)) { + $result[] = array('type' => $matches[1]); + } + elseif (preg_match('/^fileinto\s+:copy/', $content)) { + $result[] = array('type' => 'fileinto_copy', 'target' => $this->_parse_string($m[sizeof($m)-1])); + } + elseif (preg_match('/^fileinto/', $content)) { + $result[] = array('type' => 'fileinto', 'target' => $this->_parse_string($m[sizeof($m)-1])); + } + elseif (preg_match('/^redirect\s+:copy/', $content)) { + $result[] = array('type' => 'redirect_copy', 'target' => $this->_parse_string($m[sizeof($m)-1])); + } + elseif (preg_match('/^redirect/', $content)) { + $result[] = array('type' => 'redirect', 'target' => $this->_parse_string($m[sizeof($m)-1])); + } + elseif (preg_match('/^(reject|ereject)\s+(.*);$/sm', $content, $matches)) { + $result[] = array('type' => $matches[1], 'target' => $this->_parse_string($matches[2])); + } + elseif (preg_match('/^(setflag|addflag)/', $content)) { + if (in_array('imap4flags', $this->supported)) + $result[] = array('type' => 'imap4flags', 'target' => $this->_parse_string($m[sizeof($m)-1])); + else + $result[] = array('type' => 'imapflags', 'target' => $this->_parse_string($m[sizeof($m)-1])); + } + elseif (preg_match('/^vacation\s+(:days\s+([0-9]+)\s+)?(:addresses\s+\[(.*?[^\\\])\]\s+)?(:subject\s+(".*?[^"\\\]")\s+)?(:handle\s+(".*?[^"\\\]")\s+)?(:from\s+(".*?[^"\\\]")\s+)?(.*);$/sm', $content, $matches)) { + $origsubject = ""; + if (substr($matches[6], -13, 12) == ": \${subject}") { + $matches[6] = trim(substr($matches[6], 0, -13)) . "\""; + $origsubject = "1"; + } + + if ($matches[10] == "\"\${from}\"") + $matches[10] = "\"auto\""; + +// if (function_exists("mb_decode_mimeheader")) $matches[5] = mb_decode_mimeheader($matches[5]); + + if (strpos($matches[11], 'Content-Type: multipart/alternative') !== false) { + $htmlmsg = true; + + preg_match('/Content-Type: text\/html; charset=([^\r\n]+).*(.+)<\/body>/sm', $matches[11], $htmlparts); + $msg = quoted_printable_decode($htmlparts[2]); + $charset = $htmlparts[1]; + } + else { + $htmlmsg = false; + $msg = $this->_parse_string($matches[11]); + $charset = $this->_parse_charset($matches[11]); + } + + // unescape lines which start is a . + $msg = preg_replace('/(^|\r?\n)\.\./', "$1.", $msg); + + $result[] = array('type' => 'vacation', + 'days' => $matches[2], + 'subject' => $this->_parse_string($matches[6]), + 'origsubject' => $origsubject, + 'from' => $this->_parse_string($matches[10]), + 'addresses' => $this->_parse_string(str_replace("\",\"", ",", $matches[4])), + 'handle' => $this->_parse_string($matches[8]), + 'msg' => $msg, + 'htmlmsg' => $htmlmsg, + 'charset' => $charset); + } + elseif (preg_match('/^notify\s+:method\s+(".*?[^"\\\]")\s+(:options\s+\[(.*?[^\\\])\]\s+)?(:from\s+(".*?[^"\\\]")\s+)?(:importance\s+(".*?[^"\\\]")\s+)?:message\s+(".*?[^"\\\]");$/sm', $content, $matches)) { + $result[] = array('type' => 'notify', + 'method' => $this->_parse_string($matches[1]), + 'options' => $this->_parse_string($matches[3]), + 'from' => $this->_parse_string($matches[5]), + 'importance' => $this->_parse_string($matches[7]), + 'msg' => $this->_parse_string($matches[8])); + } + elseif (preg_match('/^notify\s+(:options\s+\[(.*?[^\\\])\]\s+)?(:from\s+(".*?[^"\\\]")\s+)?(:importance\s+(".*?[^"\\\]")\s+)?:message\s+(".*?[^"\\\]")\s+(.*);$/sm', $content, $matches)) { + $result[] = array('type' => 'enotify', + 'method' => $this->_parse_string($matches[8]), + 'options' => $this->_parse_string($matches[2]), + 'from' => $this->_parse_string($matches[4]), + 'importance' => $this->_parse_string($matches[6]), + 'msg' => $this->_parse_string($matches[7])); + } + elseif (preg_match('/^addheader/', $content)) { + $result[] = array('type' => 'editheaderadd', + 'index' => $m[sizeof($m)-3], + 'name' => $this->_parse_string($m[sizeof($m)-2]), + 'value' => $this->_parse_string($m[sizeof($m)-1])); + } + elseif (preg_match('/^deleteheader/', $content)) { + $result[] = array('type' => 'editheaderrem', + 'index' => $m[sizeof($m)-6] == 'last' ? $m[sizeof($m)-6] : $m[sizeof($m)-5], + 'operator' => $m[sizeof($m)-3], + 'name' => strlen($m[sizeof($m)-2]) == 0 ? $this->_parse_string($m[sizeof($m)-1]) : $this->_parse_string($m[sizeof($m)-2]), + 'value' => strlen($m[sizeof($m)-2]) == 0 ? '' : $this->_parse_string($m[sizeof($m)-1])); + } + } + } + + return $result; + } + + private function _parse_tests($content) + { + $result = NULL; + + // lists + if (preg_match('/^(allof|anyof)\s+\((.*)\)$/sm', $content, $matches)) { + $content = $matches[2]; + $join = $matches[1]=='allof' ? true : false; + } + else { + $join = false; + } + + // supported tests regular expressions + $patterns[] = '(not\s+)?(exists)\s+\[(.*?[^\\\])\]'; + $patterns[] = '(not\s+)?(exists)\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(true)'; + $patterns[] = '(not\s+)?(size)\s+:(under|over)\s+([0-9]+[KGM]{0,1})'; + $patterns[] = '(not\s+)?(spamtest|virustest)\s+:value\s+"(eq|ge|le)"\s+:comparator\s+"i;ascii-numeric"\s+"(.*?[^\\\])"'; + $patterns[] = '(not\s+)?(header|address|envelope)\s+:(contains|is|matches|regex|user|detail|domain)((\s+))\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]'; + $patterns[] = '(not\s+)?(header|address|envelope)\s+:(contains|is|matches|regex|user|detail|domain)((\s+))(".*?[^\\\]")\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(header|address|envelope)\s+:(contains|is|matches|regex|user|detail|domain)((\s+))\[(.*?[^\\\]")\]\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(header|address|envelope)\s+:(contains|is|matches|regex|user|detail|domain)((\s+))(".*?[^\\\]")\s+\[(.*?[^\\\]")\]'; + $patterns[] = '(not\s+)?(header|address|envelope)\s+:(count\s+".*?[^\\\]"|value\s+".*?[^\\\]")(\s+:comparator\s+"(.*?[^\\\])")?\s+\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]'; + $patterns[] = '(not\s+)?(header|address|envelope)\s+:(count\s+".*?[^\\\]"|value\s+".*?[^\\\]")(\s+:comparator\s+"(.*?[^\\\])")?\s+(".*?[^\\\]")\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(header|address|envelope)\s+:(count\s+".*?[^\\\]"|value\s+".*?[^\\\]")(\s+:comparator\s+"(.*?[^\\\])")?\s+\[(.*?[^\\\]")\]\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(header|address|envelope)\s+:(count\s+".*?[^\\\]"|value\s+".*?[^\\\]")(\s+:comparator\s+"(.*?[^\\\])")?\s+(".*?[^\\\]")\s+\[(.*?[^\\\]")\]'; + $patterns[] = '(not\s+)?(body)(\s+:(raw|text|content\s+".*?[^\\\]"))?\s+:(contains|is|matches|regex)((\s+))\[(.*?[^\\\]")\]'; + $patterns[] = '(not\s+)?(body)(\s+:(raw|text|content\s+".*?[^\\\]"))?\s+:(contains|is|matches|regex)((\s+))(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(body)(\s+:(raw|text|content\s+".*?[^\\\]"))?\s+:(count\s+".*?[^\\\]"|value\s+".*?[^\\\]")(\s+:comparator\s+"(.*?[^\\\])")?\s+\[(.*?[^\\\]")\]'; + $patterns[] = '(not\s+)?(body)(\s+:(raw|text|content\s+".*?[^\\\]"))?\s+:(count\s+".*?[^\\\]"|value\s+".*?[^\\\]")(\s+:comparator\s+"(.*?[^\\\])")?\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(date|currentdate)(\s+:zone\s+"([\+\-][0-9]{4})")?\s+:(contains|is|matches|regex)((\s+))(".*?[^\\\]"\s+)?(".*?[^\\\]")\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(date|currentdate)(\s+:zone\s+"([\+\-][0-9]{4})")?\s+:(count\s+".*?[^\\\]"|value\s+".*?[^\\\]")(\s+:comparator\s+"(.*?[^\\\])")?(\s+".*?[^\\\]")?\s+(".*?[^\\\]")\s+(".*?[^\\\]")'; + + // join patterns... + $pattern = '/(' . implode(')|(', $patterns) . ')/'; + + // ...and parse tests list + if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $size = sizeof($match); + + if (preg_match('/^(not\s+)?size/', $match[0])) { + $result[] = array( + 'type' => 'size', + 'not' => $match[$size-4] ? true : false, + 'operator' => $match[$size-2], // under/over + 'target' => $match[$size-1], // value + ); + } + elseif (preg_match('/^(not\s+)?(spamtest|virustest)/', $match[0])) { + $result[] = array( + 'type' => $match[$size-3], + 'not' => $match[$size-4] ? true : false, + 'operator' => $match[$size-2], // ge/le/eq + 'target' => $match[$size-1], // value + ); + } + elseif (preg_match('/^(not\s+)?(header|address|envelope)/', $match[0])) { + $result[] = array( + 'type' => $match[$size-6], + 'not' => $match[$size-7] ? true : false, + 'operator' => $match[$size-5], // is/contains/matches + 'header' => $this->_parse_list($match[$size-2]), // header(s) + 'target' => $this->_parse_list($match[$size-1], ($match[$size-5] == 'regex' ? true : false)), // string(s) + 'comparator' => trim($match[$size-3]) + ); + } + elseif (preg_match('/^(not\s+)?exists/', $match[0])) { + $result[] = array( + 'type' => 'exists', + 'not' => $match[$size-3] ? true : false, + 'operator' => 'exists', + 'header' => $this->_parse_list($match[$size-1]), // header(s) + ); + } + elseif (preg_match('/^(not\s+)?true/', $match[0])) { + $result[] = array( + 'type' => 'true', + 'not' => $match[$size-2] ? true : false, + ); + } + elseif (preg_match('/^(not\s+)?body/', $match[0])) { + if (preg_match('/.*content\s+"(.*?[^\\\])".*/', $match[$size-5], $parts)) { + $bodypart = 'content'; + $contentpart = $parts[1]; + } + else { + $bodypart = $match[$size-5]; + $contentpart = ''; + } + + $result[] = array( + 'type' => 'body', + 'not' => $match[$size-8] ? true : false, + 'bodypart' => $bodypart, + 'contentpart' => $contentpart, + 'operator' => $match[$size-4], // is/contains/matches + 'header' => 'body', // header(s) + 'target' => $this->_parse_list($match[$size-1], ($match[$size-4] == 'regex' ? true : false)), // string(s) + 'comparator' => trim($match[$size-2]) + ); + } + elseif (preg_match('/^(not\s+)?(date|currentdate)/', $match[0])) { + $result[] = array( + 'type' => 'date', + 'not' => $match[$size-10] ? true : false, + 'header' => $match[$size-9], // header + 'operator' => $match[$size-6], // is/contains/matches + 'datepart' => $this->_parse_list($match[$size-2]), + 'target' => $this->_parse_list($match[$size-1], ($match[$size-5] == 'regex' ? true : false)), // string(s) + 'field' => $match[$size-3], // received + 'comparator' => trim($match[$size-4]) + ); + } + } + } + + return array($result, $join); + } + + private function _parse_string($content) + { + $text = ''; + $content = trim($content); + + if (preg_match('/^:mime\s+text:(.*)\.$/sm', $content, $matches)) { + $parts = preg_split("/\r?\n/", $matches[1], 4); + $text = trim($parts[3]); + } + elseif (preg_match('/^text:(.*)\.$/sm', $content, $matches)) + $text = trim($matches[1]); + elseif (preg_match('/^"(.*)"$/', $content, $matches)) + $text = str_replace('\"', '"', $matches[1]); + + return $text; + } + + private function _parse_charset($content) + { + $charset = RCUBE_CHARSET; + $content = trim($content); + + if (preg_match('/^:mime\s+text:(.*)\.$/sm', $content, $matches)) { + $parts = preg_split("/\r?\n/", $matches[1], 4); + + $charset = trim(substr($parts[1], stripos($parts[1], "charset=") + 8)); + } + + return $charset; + } + + private function _escape_string($content) + { + $replace['/"/'] = '\\"'; + + if (is_array($content)) { + for ($x=0, $y=sizeof($content); $x<$y; $x++) + $content[$x] = preg_replace(array_keys($replace), array_values($replace), $content[$x]); + + return $content; + } + else { + return preg_replace(array_keys($replace), array_values($replace), $content); + } + } + + private function _parse_list($content, $regex = false) + { + $result = array(); + + if ($regex) { + if (preg_match('/^"(.*)"$/', $content, $matches)); + $content = $matches[1]; + + $content = str_replace('\"', '"', $content); + return $content; + } + + for ($x=0, $len=strlen($content); $x<$len; $x++) { + switch ($content[$x]) { + case '\\': + $str .= $content[++$x]; + + break; + case '"': + if (isset($str)) { + $result[] = $str; + unset($str); + } + else { + $str = ''; + } + + break; + default: + if (isset($str)) + $str .= $content[$x]; + + break; + } + } + + if (sizeof($result)>1) + return $result; + elseif (sizeof($result) == 1) + return $result[0]; + else + return NULL; + } + + private function _safe_serial($data) + { + $data = str_replace("\r", "[!r]", $data); + $data = str_replace("\n", "[!n]", $data); + return $data; + } + + private function _regular_serial($data) + { + $data = str_replace("[!r]", "\r", $data); + $data = str_replace("[!n]", "\n", $data); + return $data; + } +} + +?> \ No newline at end of file -- cgit v1.2.3