/include/rcube_sieve_script.php
PHP | 969 lines | 787 code | 129 blank | 53 comment | 137 complexity | 74b87770ae2e8f7f01d0d96e3649f21b MD5 | raw file
- <?php
- /*
- +-----------------------------------------------------------------------+
- | rcube_sieve_script class for sieverules script parsing |
- | |
- | Author: Aleksander Machniak <alec@alec.pl> |
- | 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(RCMAIL_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;
- $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'] = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">' .
- "\r\n<html><body>\r\n" . $action['msg'] . "\r\n</body></html>\r\n";
- $MAIL_MIME->setHTMLBody($action['msg']);
- // add a plain text version of the e-mail as an alternative part.
- $h2t = new 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[5], -13, 12) == ": \${subject}") {
- $matches[5] = trim(substr($matches[5], 0, -13)) . "\"";
- $origsubject = "1";
- }
- if ($matches[9] == "\"\${from}\"")
- $matches[9] = "\"auto\"";
- // if (function_exists("mb_decode_mimeheader")) $matches[5] = mb_decode_mimeheader($matches[5]);
- if (strpos($matches[10], 'Content-Type: multipart/alternative') !== false) {
- $htmlmsg = true;
- preg_match('/Content-Type: text\/html; charset=([^\r\n]+).*<body>(.+)<\/body>/sm', $matches[10], $htmlparts);
- $msg = quoted_printable_decode($htmlparts[2]);
- $charset = $htmlparts[1];
- }
- else {
- $htmlmsg = false;
- $msg = $this->_parse_string($matches[10]);
- $charset = $this->_parse_charset($matches[10]);
- }
- // unescape lines which start is a .
- $msg = preg_replace('/(^|\r?\n)\.\./', "$1.", $msg);
- $result[] = array('type' => 'vacation',
- 'days' => $matches[1],
- 'subject' => $this->_parse_string($matches[5]),
- 'origsubject' => $origsubject,
- 'from' => $this->_parse_string($matches[9]),
- 'addresses' => $this->_parse_string(str_replace("\",\"", ",", $matches[3])),
- 'handle' => $this->_parse_string($matches[7]),
- '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 = RCMAIL_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;
- }
- }
- ?>