summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: e4a6a98)
raw | patch | inline | side by side (parent: e4a6a98)
author | hickert <hickert@594d385d-05f5-0310-b6e9-bd551577e9d8> | |
Thu, 15 Feb 2007 11:27:08 +0000 (11:27 +0000) | ||
committer | hickert <hickert@594d385d-05f5-0310-b6e9-bd551577e9d8> | |
Thu, 15 Feb 2007 11:27:08 +0000 (11:27 +0000) |
git-svn-id: https://oss.gonicus.de/repositories/gosa/trunk@5722 594d385d-05f5-0310-b6e9-bd551577e9d8
include/class_sieve.inc | [deleted file] | patch | blob | history |
include/sieve/class_parser.inc | [new file with mode: 0644] | patch | blob |
include/sieve/class_scanner.inc | [new file with mode: 0644] | patch | blob |
include/sieve/class_semantics.inc | [new file with mode: 0644] | patch | blob |
include/sieve/class_sieve.inc | [new file with mode: 0644] | patch | blob |
include/sieve/class_sievescript.php | [new file with mode: 0644] | patch | blob |
include/sieve/class_tree.inc | [new file with mode: 0644] | patch | blob |
include/sieve/libsieve.php | [new file with mode: 0644] | patch | blob |
plugins/personal/mail/class_mailAccount.inc | patch | blob | history |
diff --git a/include/class_sieve.inc b/include/class_sieve.inc
--- a/include/class_sieve.inc
+++ /dev/null
@@ -1,514 +0,0 @@
-<?
-
-/*
- * $Id: class_sieve.inc,v 1.1 2005/02/21 09:33:01 cajus Exp $
- *
- * Copyright 2001 Dan Ellis <danellis@rushmore.com>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- */
-
-// TODO before next release: remove ::status() and dependencies
-
-
-define ("F_NO", 0);
-define ("F_OK", 1);
-define ("F_DATA", 2);
-define ("F_HEAD", 3);
-
-define ("EC_NOT_LOGGED_IN", 0);
-define ("EC_QUOTA", 10);
-define ("EC_NOSCRIPTS", 20);
-define ("EC_UNKNOWN", 255);
-/*
-
-SIEVE-PHP.LIB VERSION 0.0.8
-
-(C) 2001 Dan Ellis.
-
-PLEASE READ THE README FILE FOR MORE INFORMATION.
-
-Basically, this is the first re-release. Things are much better than before.
-
-Notes:
-This program/libary has bugs.
- . This was quickly hacked out, so please let me know what is wrong and if you feel ambitious submit
- a patch :).
-
-Todo:
- . Provide better error diagnostics. (mostly done with ver 0.0.5)
- . Allow other auth mechanisms besides plain (in progress)
- . Have timing mechanism when port problems arise. (not done yet)
- . Maybe add the NOOP function. (not done yet)
- . Other top secret stuff.... (some done, believe me?)
-
-Dan Ellis (danellis@rushmore.com)
-
-This program is released under the GNU Public License.
-
-You should have received a copy of the GNU Public
- License along with this package; if not, write to the
- Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- Boston, MA 02111-1307, USA.
-
-See CHANGES for updates since last release
-
-Contributers of patches:
- Atif Ghaffar
- Andrew Sterling Hanenkamp <sterling@hanenkamp.com>
-*/
-
-
-class sieve
-{
- var $host;
- var $port;
- var $user;
- var $pass;
- var $auth_types; /* a comma seperated list of allowed auth types, in order of preference */
- var $auth_in_use; /* type of authentication attempted */
-
- var $line;
- var $fp;
- var $retval;
- var $tmpfile;
- var $fh;
- var $len;
- var $script;
-
- var $loggedin;
- var $capabilities;
- var $error;
- var $error_raw;
- var $responses;
-
- //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN)
- //so we can decide how to handle certain errors?!?
-
- //also add a connection type, like PLAIN, MD5, etc...
-
-
- function get_response()
- {
- if($this->loggedin == false or feof($this->fp)){
- $this->error = EC_NOT_LOGGED_IN;
- $this->error_raw = "You are not logged in.";
- return false;
- }
-
- unset($this->response);
- unset($this->error);
- unset($this->error_raw);
-
- $this->line=fgets($this->fp,1024);
- $this->token = split(" ", $this->line, 2);
-
- if($this->token[0] == "NO"){
- /* we need to try and extract the error code from here. There are two possibilites: one, that it will take the form of:
- NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */
- $this->x = 0;
- list($this->ltoken, $this->mtoken, $this->rtoken) = split(" ", $this->line." ", 3);
- if($this->mtoken[0] == "{"){
- while($this->mtoken[$this->x] != "}" or $this->err_len < 1){
- $this->err_len = substr($this->mtoken, 1, $this->x);
- $this->x++;
- }
- //print "<br>Trying to receive $this->err_len bytes for result<br>";
- $this->line = fgets($this->fp,$this->err_len);
- $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
- $this->err_recv = strlen($this->line);
-
- while($this->err_recv < $this->err_len){
- //print "<br>Trying to receive ".($this->err_len-$this->err_recv)." bytes for result<br>";
- $this->line = fgets($this->fp, ($this->err_len-$this->err_recv));
- $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
- $this->err_recv += strlen($this->line);
- } /* end while */
- $this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug...
- $this->error=EC_UNKNOWN;
-
- } /* end if */
- elseif($this->mtoken[0] == "("){
- switch($this->mtoken){
- case "(\"QUOTA\")":
- $this->error = EC_QUOTA;
- $this->error_raw=$this->rtoken;
- break;
- default:
- $this->error = EC_UNKNOWN;
- $this->error_raw=$this->rtoken;
- break;
- } /* end switch */
- } /* end elseif */
- else{
- $this->error = EC_UNKNOWN;
- $this->error_raw = $this->line;
- }
- return false;
-
- } /* end if */
- elseif(substr($this->token[0],0,-2) == "OK"){
- return true;
- } /* end elseif */
- elseif($this->token[0][0] == "{"){
-
- /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */
-
- /* the first line is the len field {xx}, which we don't care about at this point */
- $this->line = fgets($this->fp,1024);
- while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
- $this->response[]=$this->line;
- $this->line = fgets($this->fp, 1024);
- }
- if(substr($this->line,0,2) == "OK")
- return true;
- else
- return false;
- } /* end elseif */
- elseif($this->token[0][0] == "\""){
-
- /* I'm going under the _assumption_ that the only function that will get here is the listscripts().
- I could very well be mistaken here, if I am, this part needs some rework */
-
- $this->found_script=false;
-
- while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
- $this->found_script=true;
- list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2);
- //hmmm, a bug in php, if there is no space on explode line, a warning is generated...
-
- if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){
- $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1);
- }
- else
- $this->response[] = substr(rtrim($this->ltoken),1,-1);
- $this->line = fgets($this->fp, 1024);
- } /* end while */
-
- return true;
-
- } /* end elseif */
- else{
- $this->error = EC_UNKNOWN;
- $this->error_raw = $this->line;
- print "<b><i>UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line</i></b><br>";
- return false;
- } /* end else */
- } /* end get_response() */
-
- function sieve($host, $port, $user, $pass, $auth="", $auth_types="PLAIN DIGEST-MD5")
- {
- $this->host=$host;
- $this->port=$port;
- $this->user=$user;
- $this->pass=$pass;
- if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */
- $this->auth = $this->user;
- else
- $this->auth = $auth;
- $this->auth_types=$auth_types; /* Allowed authentication types */
- $this->fp=0;
- $this->line="";
- $this->retval="";
- $this->tmpfile="";
- $this->fh=0;
- $this->len=0;
- $this->capabilities="";
- $this->loggedin=false;
- $this->error= "";
- $this->error_raw="";
- }
-
- function parse_for_quotes($string)
- {
- /* This function tokenizes a line of input by quote marks and returns them as an array */
-
- $start = -1;
- $index = 0;
-
- for($ptr = 0; $ptr < strlen($string); $ptr++){
- if($string[$ptr] == '"' and $string[$ptr] != '\\'){
- if($start == -1){
- $start = $ptr;
- } /* end if */
- else{
- $token[$index++] = substr($string, $start + 1, $ptr - $start - 1);
- $found = true;
- $start = -1;
- } /* end else */
-
- } /* end if */
-
- } /* end for */
-
- if(isset($token))
- return $token;
- else
- return false;
- } /* end function */
-
- function status($string)
- {
- //this should probably be replaced by a smarter parser.
-
- /* Need to remove this and all dependencies from the class */
-
- switch (substr($string, 0,2)){
- case "NO":
- return F_NO; //there should be some function to extract the error code from this line
- //NO ("quota") "You are oly allowed x number of scripts"
- break;
- case "OK":
- return F_OK;
- break;
- default:
- switch ($string[0]){
- case "{":
- //do parse here for {}'s maybe modify parse_for_quotes to handle any parse delimiter?
- return F_HEAD;
- break;
- default:
- return F_DATA;
- break;
- } /* end switch */
- } /* end switch */
- } /* end status() */
-
- function sieve_login()
- {
-
- $this->fp=fsockopen($this->host,$this->port);
- if($this->fp == false)
- return false;
-
- $this->line=fgets($this->fp,1024);
-
- //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard
- //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
- //So, if we see IMLEMENTATION in the first line, then we are done.
-
- if(ereg("IMPLEMENTATION",$this->line))
- {
- //we're on the Cyrus V2 sieve server
- while(sieve::status($this->line) == F_DATA){
-
- $this->item = sieve::parse_for_quotes($this->line);
-
- if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
- $this->capabilities["implementation"] = $this->item[1];
-
- elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
-
- if(strcmp($this->item[0], "SIEVE") == 0)
- $this->cap_type="modules";
- else
- $this->cap_type="auth";
-
- $this->modules = split(" ", $this->item[1]);
- if(is_array($this->modules)){
- foreach($this->modules as $this->module)
- $this->capabilities[$this->cap_type][$this->module]=true;
- } /* end if */
- elseif(is_string($this->modules))
- $this->capabilites[$this->cap_type][$this->modules]=true;
- }
- else{
- $this->capabilities["unknown"][]=$this->line;
- }
- $this->line=fgets($this->fp,1024);
-
- }// end while
- }
- else
- {
- //we're on the older Cyrus V1. server
- //this version does not support module reporting. We only have auth types.
- $this->cap_type="auth";
-
- //break apart at the "Cyrus timsieve...." "SASL={......}"
- $this->item = sieve::parse_for_quotes($this->line);
-
- $this->capabilities["implementation"] = $this->item[0];
-
- //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz}
- $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
-
- //then split again at the ", " stuff.
- $this->modules = split($this->modules, ", ");
-
- //fill up our $this->modules property
- if(is_array($this->modules)){
- foreach($this->modules as $this->module)
- $this->capabilities[$this->cap_type][$this->module]=true;
- } /* end if */
- elseif(is_string($this->modules))
- $this->capabilites[$this->cap_type][$this->module]=true;
- }
-
-
-
-
- if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes?
- $this->error=EC_UNKNOWN;
- $this->error_raw = "Server not allowing connections.";
- return false;
- }
-
- /* decision login to decide what type of authentication to use... */
-
- /* Loop through each allowed authentication type and see if the server allows the type */
- foreach(split(" ",$this->auth_types) as $auth_type)
- {
- if ($this->capabilities["auth"][$auth_type])
- {
- /* We found an auth type that is allowed. */
- $this->auth_in_use = $auth_type;
- break;
- }
- }
-
- /* Fill error message if no auth types are present */
- if (!isset($this->capabilities["auth"])){
- $this->error=EC_UNKNOWN;
- $this->error_raw = "No authentication methods found - please check your sieve setup for missing sasl modules";
- return false;
- }
-
- /* call our authentication program */
- return sieve::authenticate();
-
- }
-
- function sieve_logout()
- {
- if($this->loggedin==false)
- return false;
-
- fputs($this->fp,"LOGOUT\r\n");
- fclose($this->fp);
- $this->loggedin=false;
- return true;
- }
-
- function sieve_sendscript($scriptname, $script)
- {
- if($this->loggedin==false)
- return false;
- $this->script=stripslashes($script);
- $len=strlen($this->script);
- fputs($this->fp, "PUTSCRIPT \"$scriptname\" {".$len."+}\r\n");
- fputs($this->fp, "$this->script\r\n");
-
- return sieve::get_response();
-
- }
-
- //it appears the timsieved does not honor the NUMBER type. see lex.c in timsieved src.
- //don't expect this function to work yet. I might have messed something up here, too.
- function sieve_havespace($scriptname, $scriptsize)
- {
- if($this->loggedin==false)
- return false;
- fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
- return sieve::get_response();
-
- }
-
- function sieve_setactivescript($scriptname)
- {
- if($this->loggedin==false)
- return false;
-
- fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");
- return sieve::get_response();
-
- }
-
- function sieve_getscript($scriptname)
- {
- unset($this->script);
- if($this->loggedin==false)
- return false;
-
- fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
- return sieve::get_response();
-
- }
-
-
- function sieve_deletescript($scriptname)
- {
- if($this->loggedin==false)
- return false;
-
- fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");
-
- return sieve::get_response();
- }
-
-
- function sieve_listscripts()
- {
- fputs($this->fp, "LISTSCRIPTS\r\n");
- sieve::get_response(); //should always return true, even if there are no scripts...
- if(isset($this->found_script) and $this->found_script)
- return true;
- else{
- $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found...
- $this->error_raw="No scripts found for this account.";
- return false;
- }
- }
-
- function sieve_alive()
- {
- if(!isset($this->fp) or $this->fp==0){
- $this->error = EC_NOT_LOGGED_IN;
- return false;
- }
- elseif(feof($this->fp)){
- $this->error = EC_NOT_LOGGED_IN;
- return false;
- }
- else
- return true;
- }
-
- function authenticate()
- {
-
- switch ($this->auth_in_use) {
-
- case "PLAIN":
- $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
-
- $this->len=strlen($auth);
- fputs($this->fp, "AUTHENTICATE \"PLAIN\" {".$this->len."+}\r\n");
- fputs($this->fp, "$auth\r\n");
-
- $this->line=fgets($this->fp,1024);
- while(sieve::status($this->line) == F_DATA)
- $this->line=fgets($this->fp,1024);
-
- if(sieve::status($this->line) == F_NO)
- return false;
- $this->loggedin=true;
- return true;
- break;
-
- default:
- return false;
- break;
-
- }//end switch
-
-
- }//end authenticate()
-
-
-}
-
-
-
-?>
diff --git a/include/sieve/class_parser.inc b/include/sieve/class_parser.inc
--- /dev/null
@@ -0,0 +1,319 @@
+<?php
+class Parser
+{
+ var $scanner_;
+ var $script_;
+ var $tree_;
+ var $status_;
+
+ var $status_text;
+
+ function parse($script)
+ {
+ $this->status_text = "incomplete";
+
+ $this->script_ = $script;
+ $this->tree_ = new Tree(Scanner::scriptStart());
+ $this->tree_->setDumpFunc(array(&$this, 'dumpToken_'));
+ $this->scanner_ = new Scanner($this->script_);
+ $this->scanner_->setCommentFunc(array($this, 'comment_'));
+
+ if ($this->commands_($this->tree_->getRoot()) &&
+ $this->scanner_->nextTokenIs('script-end'))
+ {
+ return $this->success_('success');
+ }
+
+ return $this->status_;
+ }
+
+ function dumpParseTree()
+ {
+ return $this->tree_->dump();
+ }
+
+ function dumpToken_(&$token)
+ {
+ if (is_array($token))
+ {
+ $str = "<" . $token['text'] . "> ";
+ foreach ($token as $k => $v)
+ {
+ $str .= " $k:$v";
+ }
+ return $str;
+ }
+
+ return strval($token);
+ }
+
+ function success_($text = null)
+ {
+ if ($text != null)
+ {
+ $this->status_text = $text;
+ }
+
+ return $this->status_ = true;
+ }
+
+ function error_($text, $token = null)
+ {
+ if ($token != null)
+ {
+ $text = 'line '. $token['line'] .': '. $token['class'] . " where $text expected near ". $token['text'];
+ }
+
+ $this->status_text = $text;
+ return $this->status_ = false;
+ }
+
+ function done_()
+ {
+ $this->status_ = true;
+ return false;
+ }
+
+ function comment_($token)
+ {
+ $this->tree_->addChild($token);
+ }
+
+ function commands_($parent_id)
+ {
+ while ($this->command_($parent_id))
+ ;
+
+ return $this->status_;
+ }
+
+ function command_($parent_id)
+ {
+ if (!$this->scanner_->nextTokenIs('identifier'))
+ {
+ if ($this->scanner_->nextTokenIs(array('block-end', 'script-end')))
+ {
+ return $this->done_();
+ }
+ return $this->error_('identifier', $this->scanner_->peekNextToken());
+ }
+
+ // Get and check a command token
+ $token = $this->scanner_->nextToken();
+ $semantics = new Semantics($token['text']);
+ if ($semantics->unknown)
+ {
+ return $this->error_('unknown command: '. $token['text']);
+ }
+
+ $last = $this->tree_->getLastNode($parent_id);
+ if (!$semantics->validAfter($last['text']))
+ {
+ return $this->error_('"'. $token['text'] .'" may not appear after "'. $last['text'] .'"');
+ }
+
+ // Process eventual arguments
+ $this_node = $this->tree_->addChildTo($parent_id, $token);
+ if ($this->arguments_($this_node, $semantics) == false)
+ {
+ return false;
+ }
+
+ $token = $this->scanner_->nextToken();
+ if ($token['class'] != 'semicolon')
+ {
+ if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
+ {
+ return $this->error_($semantics->message);
+ }
+
+ if ($token['class'] == 'block-start')
+ {
+ $this->tree_->addChildTo($this_node, $token);
+ $ret = $this->block_($this_node, $semantics);
+ return $ret;
+ }
+
+ return $this->error_('semicolon', $token);
+ }
+
+ $this->tree_->addChildTo($this_node, $token);
+ return $this->success_();
+ }
+
+ function arguments_($parent_id, &$semantics)
+ {
+ while ($this->argument_($parent_id, &$semantics))
+ ;
+
+ if ($this->status_ == true)
+ {
+ $this->testlist_($parent_id, $semantics);
+ }
+
+ return $this->status_;
+ }
+
+ function argument_($parent_id, &$semantics)
+ {
+ if ($this->scanner_->nextTokenIs(array('number', 'tag')))
+ {
+ // Check if semantics allow a number or tag
+ $token = $this->scanner_->nextToken();
+ if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
+ {
+ return $this->error_($semantics->message);
+ }
+
+ $this->tree_->addChildTo($parent_id, $token);
+ return $this->success_();
+ }
+
+ return $this->stringlist_($parent_id, &$semantics);
+ }
+
+ function stringlist_($parent_id, &$semantics)
+ {
+ if (!$this->scanner_->nextTokenIs('left-bracket'))
+ {
+ return $this->string_($parent_id, &$semantics);
+ }
+
+ $token = $this->scanner_->nextToken();
+ if (!$semantics->startStringList($token['line']))
+ {
+ return $this->error_($semantics->message);
+ }
+ $this->tree_->addChildTo($parent_id, $token);
+
+ while ($token['class'] != 'right-bracket')
+ {
+ if (!$this->string_($parent_id, &$semantics))
+ {
+ return $this->status_;
+ }
+
+ $token = $this->scanner_->nextToken();
+
+ if ($token['class'] != 'comma' && $token['class'] != 'right-bracket')
+ {
+ return $this->error_('comma or closing bracket', $token);
+ }
+
+ $this->tree_->addChildTo($parent_id, $token);
+ }
+
+ $semantics->endStringList();
+ return $this->success_();
+ }
+
+ function string_($parent_id, &$semantics)
+ {
+ if (!$this->scanner_->nextTokenIs(array('quoted-string', 'multi-line')))
+ {
+ return $this->done_();
+ }
+
+ $token = $this->scanner_->nextToken();
+ if (!$semantics->validToken('string', $token['text'], $token['line']))
+ {
+ return $this->error_($semantics->message);
+ }
+
+ $this->tree_->addChildTo($parent_id, $token);
+ return $this->success_();
+ }
+
+ function testlist_($parent_id, &$semantics)
+ {
+ if (!$this->scanner_->nextTokenIs('left-parant'))
+ {
+ return $this->test_($parent_id, $semantics);
+ }
+
+ $token = $this->scanner_->nextToken();
+ if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
+ {
+ return $this->error_($semantics->message);
+ }
+ $this->tree_->addChildTo($parent_id, $token);
+
+ while ($token['class'] != 'right-parant')
+ {
+ if (!$this->test_($parent_id, $semantics))
+ {
+ return $this->status_;
+ }
+
+ $token = $this->scanner_->nextToken();
+
+ if ($token['class'] != 'comma' && $token['class'] != 'right-parant')
+ {
+ return $this->error_('comma or closing paranthesis', $token);
+ }
+
+ $this->tree_->addChildTo($parent_id, $token);
+ }
+
+ return $this->success_();
+ }
+
+ function test_($parent_id, &$semantics)
+ {
+ if (!$this->scanner_->nextTokenIs('identifier'))
+ {
+ // There is no test
+ return $this->done_();
+ }
+
+ // Check if semantics allow an identifier
+ $token = $this->scanner_->nextToken();
+ if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
+ {
+ return $this->error_($semantics->message);
+ }
+
+ // Get semantics for this test command
+ $this_semantics = new Semantics($token['text']);
+ if ($this_semantics->unknown)
+ {
+ return $this->error_('unknown test: '. $token['text']);
+ }
+
+ $this_node = $this->tree_->addChildTo($parent_id, $token);
+
+ // Consume eventual argument tokens
+ if (!$this->arguments_($this_node, $this_semantics))
+ {
+ return false;
+ }
+
+ // Check if arguments were all there
+ $token = $this->scanner_->peekNextToken();
+ if (!$this_semantics->done($token['class'], $token['text'], $token['line']))
+ {
+ return $this->error_($this_semantics->message);
+ }
+
+ return true;
+ }
+
+ function block_($parent_id, &$semantics)
+ {
+ if ($this->commands_($parent_id, $semantics))
+ {
+ $token = $this->scanner_->nextToken();
+
+ if ($token['class'] != 'block-end')
+ {
+ return $this->error_('closing curly brace', $token);
+ }
+
+ $this->tree_->addChildTo($parent_id, $token);
+ return $this->success_();
+ }
+ return $this->status_;
+ }
+}
+
+?>
diff --git a/include/sieve/class_scanner.inc b/include/sieve/class_scanner.inc
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+class Scanner
+{
+ function Scanner(&$script)
+ {
+ $this->_construct($script);
+ }
+
+ function _construct(&$script)
+ {
+ if ($script === null)
+ {
+ return;
+ }
+
+ $this->tokenize($script);
+ }
+
+ function setCommentFunc($callback)
+ {
+ if ($callback == null || is_callable($callback))
+ {
+ $this->commentFn_ = $callback;
+ }
+ }
+
+ function tokenize(&$script)
+ {
+ $pos = 0;
+ $line = 1;
+ $script_length = mb_strlen($script);
+
+ while ($pos < $script_length)
+ {
+ foreach ($this->token_match_ as $class => $regex)
+ {
+ if (preg_match('/^'. $regex .'/', mb_substr($script, $pos), $match))
+ {
+ $length = mb_strlen($match[0]);
+
+ if ($class != 'whitespace')
+ {
+ array_push($this->tokens_, array(
+ 'class' => $class,
+ 'text' => chop(mb_substr($script, $pos, $length)),
+ 'line' => $line,
+ ));
+ }
+ if ($class == 'unknown')
+ {
+ return;
+ }
+
+ $pos += $length;
+ $line += mb_substr_count($match[0], "\n");
+ break;
+ }
+ }
+ }
+
+ array_push($this->tokens_, array(
+ 'class' => 'script-end',
+ 'text' => 'script-end',
+ 'line' => $line,
+ ));
+ }
+
+ function nextTokenIs($class)
+ {
+ $offset = 0;
+ do
+ {
+ $next = $this->tokens_[$this->tokenPos_ + $offset++]['class'];
+ }
+ while ($next == 'comment');
+
+ if (is_array($class))
+ {
+ return in_array($next, $class);
+ }
+ else if (is_string($class))
+ {
+ return (strcmp($next, $class) == 0);
+ }
+ return false;
+ }
+
+ function peekNextToken()
+ {
+ return $this->tokens_[$this->tokenPos_];
+ }
+
+ function nextToken()
+ {
+ $token = $this->tokens_[$this->tokenPos_++];
+ while ($token['class'] == 'comment')
+ {
+ if ($this->commentFn_ != null)
+ {
+ call_user_func($this->commentFn_, $token);
+ }
+ $token = $this->tokens_[$this->tokenPos_++];
+ }
+ return $token;
+ }
+
+ function scriptStart()
+ {
+ return array(
+ 'class' => 'script-start',
+ 'text' => 'script-start',
+ 'line' => 1,
+ );
+ }
+
+ var $commentFn_ = null;
+ var $tokenPos_ = 0;
+ var $tokens_ = array();
+ var $token_match_ = array (
+ 'left-bracket' => '\[',
+ 'right-bracket' => '\]',
+ 'block-start' => '\{',
+ 'block-end' => '\}',
+ 'left-parant' => '\(',
+ 'right-parant' => '\)',
+ 'comma' => ',',
+ 'semicolon' => ';',
+ 'whitespace' => '[ \r\n\t]+',
+ 'tag' => ':[[:alpha:]_][[:alnum:]_]*(?=\b)',
+ 'quoted-string' => '"(?:\\[\\"]|[^\x00"])*"',
+ 'number' => '[[:digit:]]+(?:[KMG])?(?=\b)',
+ 'comment' => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?\n)',
+ 'multi-line' => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.]*\r?\n)*\.\r?\n',
+ 'identifier' => '[[:alpha:]_][[:alnum:]_]*(?=\b)',
+ 'unknown token' => '[^ \r\n\t]+'
+ );
+}
+
+?>
\ No newline at end of file
diff --git a/include/sieve/class_semantics.inc b/include/sieve/class_semantics.inc
--- /dev/null
@@ -0,0 +1,553 @@
+<?php
+
+$requires_ = array();
+
+class Semantics
+{
+ var $command_;
+ var $comparator_;
+ var $matchType_;
+ var $s_;
+ var $unknown;
+ var $message;
+ var $testCommands_ = '(address|envelope|header|size|allof|anyof|exists|not|true|false)';
+ var $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress)';
+
+ function Semantics($command)
+ {
+ $this->command_ = $command;
+ $this->unknown = false;
+ switch ($command)
+ {
+
+ /********************
+ * control commands
+ */
+ case 'require':
+ /* require <capabilities: string-list> */
+ $this->s_ = array(
+ 'valid_after' => array('script-start', 'require'),
+ 'arguments' => array(
+ array('class' => 'string', 'list' => true, 'name' => 'require-string', 'occurrences' => '1', 'call' => 'setRequire_', 'values' => array(
+ array('occurrences' => '+', 'regex' => '"'. $this->requireStrings_ .'"'),
+ array('occurrences' => '+', 'regex' => '"comparator-i;(octet|ascii-casemap|ascii-numeric)"')
+ ))
+ )
+ );
+ break;
+
+ case 'if':
+ /* if <test> <block> */
+ $this->s_ = array(
+ 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else',
+ 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'arguments' => array(
+ array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
+ )),
+ array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
+ ))
+ )
+ );
+ break;
+
+ case 'elsif':
+ /* elsif <test> <block> */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif'),
+ 'arguments' => array(
+ array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
+ )),
+ array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
+ ))
+ )
+ );
+ break;
+
+ case 'else':
+ /* else <block> */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif'),
+ 'arguments' => array(
+ array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
+ ))
+ )
+ );
+ break;
+
+
+ /*******************
+ * action commands
+ */
+ case 'keep':
+ case 'stop':
+ case 'discard':
+ /* keep / stop / discard */
+ $this->s_ = array(
+ 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else',
+ 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard')
+ );
+ break;
+
+ case 'fileinto':
+ /* fileinto <folder: string> */
+ $this->s_ = array(
+ 'requires' => 'fileinto',
+ 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'arguments' => array(
+ array('class' => 'string', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '".*"', 'name' => 'folder')
+ ))
+ )
+ );
+ break;
+
+ case 'redirect':
+ /* redirect <address: string> */
+ $this->s_ = array(
+ 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'arguments' => array(
+ array('class' => 'string', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '".*"', 'name' => 'address')
+ ))
+ )
+ );
+ break;
+
+ case 'reject':
+ /* reject <reason: string> */
+ $this->s_ = array(
+ 'requires' => 'reject',
+ 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'arguments' => array(
+ array('class' => 'string', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason')
+ ))
+ )
+ );
+ break;
+
+ case 'vacation':
+ /* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] <reason: string> */
+ $this->s_ = array(
+ 'requires' => 'vacation',
+ 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '*', 'values' => array(
+ array('occurrences' => '?', 'regex' => ':days', 'name' => 'days',
+ 'add' => array(
+ array('class' => 'number', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '.*', 'name' => 'period')
+ ))
+ )
+ ),
+ array('occurrences' => '?', 'regex' => ':addresses', 'name' => 'addresses',
+ 'add' => array(
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'address')
+ ))
+ )
+ ),
+ array('occurrences' => '?', 'regex' => ':subject', 'name' => 'subject',
+ 'add' => array(
+ array('class' => 'string', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '".*"', 'name' => 'subject')
+ ))
+ )
+ ),
+ array('occurrences' => '?', 'regex' => ':mime', 'name' => 'mime')
+ )),
+ array('class' => 'string', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason')
+ ))
+ )
+ );
+ break;
+
+
+ /*****************
+ * test commands
+ */
+ case 'address':
+ /* address [address-part: tag] [comparator: tag] [match-type: tag] <header-list: string-list> <key-list: string-list> */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
+ array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
+ 'add' => array(
+ array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
+ array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
+ array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
+ ))
+ )
+ )
+ )),
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
+ )),
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
+ ))
+ )
+ );
+ break;
+
+ case 'allof':
+ case 'anyof':
+ /* allof <tests: test-list>
+ anyof <tests: test-list> */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'arguments' => array(
+ array('class' => 'left-parant', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '\(', 'name' => 'test-list')
+ )),
+ array('class' => 'identifier', 'occurrences' => '+', 'values' => array(
+ array('occurrences' => '+', 'regex' => $this->testCommands_, 'name' => 'test')
+ ))
+ )
+ );
+ break;
+
+ case 'envelope':
+ /* envelope [address-part: tag] [comparator: tag] [match-type: tag] <envelope-part: string-list> <key-list: string-list> */
+ $this->s_ = array(
+ 'requires' => 'envelope',
+ 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
+ array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
+ 'add' => array(
+ array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
+ array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
+ array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
+ ))
+ )
+ )
+ )),
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'envelope-part')
+ )),
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
+ ))
+ )
+ );
+ break;
+
+ case 'exists':
+ /* exists <header-names: string-list> */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'arguments' => array(
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
+ ))
+ )
+ );
+ break;
+
+ case 'header':
+ /* header [comparator: tag] [match-type: tag] <header-names: string-list> <key-list: string-list> */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
+ 'add' => array(
+ array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
+ array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
+ array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
+ ))
+ )
+ )
+ )),
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
+ )),
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
+ ))
+ )
+ );
+ break;
+
+ case 'not':
+ /* not <test> */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'arguments' => array(
+ array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
+ ))
+ )
+ );
+ break;
+
+ case 'size':
+ /* size <":over" / ":under"> <limit: number> */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => ':(over|under)', 'name' => 'size-type')
+ )),
+ array('class' => 'number', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '.*', 'name' => 'limit')
+ ))
+ )
+ );
+ break;
+
+ case 'true':
+ case 'false':
+ /* true / false */
+ $this->s_ = array(
+ 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not')
+ );
+ break;
+
+
+ /********************
+ * unknown commands
+ */
+ default:
+ $this->unknown = true;
+ }
+ }
+
+ function setRequire_($text)
+ {
+ global $requires_;
+ array_push($requires_, $text);
+ return true;
+ }
+
+ function setMatchType_($text)
+ {
+ // Do special processing for relational test extension
+ if ($text == ':count' || $text == ':value')
+ {
+ global $requires_;
+ if (!in_array('"relational"', $requires_))
+ {
+ $this->message = 'missing require for match-type '. $text;
+ return false;
+ }
+
+ array_unshift($this->s_['arguments'],
+ array('class' => 'string', 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '1', 'regex' => '"(lt|le|eq|ge|gt|ne)"', 'name' => 'relation-string'),
+ ))
+ );
+ }
+ $this->matchType_ = $text;
+ return true;
+ }
+
+ function setComparator_($text)
+ {
+ $this->comparator_ = $text;
+ return true;
+ }
+
+ function checkAddrPart_($text)
+ {
+ if ($text == ':user' || $text == ':detail')
+ {
+ global $requires_;
+ if (!in_array('"subaddress"', $requires_))
+ {
+ $this->message = 'missing require for tag '. $text;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function checkTags_()
+ {
+ if (isset($this->matchType_) &&
+ $this->matchType_ == ':count' &&
+ $this->comparator_ != '"i;ascii-numeric"')
+ {
+ $this->message = 'match-type :count needs comparator i;ascii-numeric';
+ return false;
+ }
+ return true;
+ }
+
+ function validAfter($prev)
+ {
+ return in_array($prev, $this->s_['valid_after']);
+ }
+
+ function validClass_($class, $id)
+ {
+ // Check if command expects any arguments
+ if (!isset($this->s_['arguments']))
+ {
+ $this->message = $id .' where semicolon expected';
+ return false;
+ }
+
+ foreach ($this->s_['arguments'] as $arg)
+ {
+ if ($class == $arg['class'])
+ {
+ return true;
+ }
+
+ // Is the argument required
+ if ($arg['occurrences'] != '?' && $arg['occurrences'] != '*')
+ {
+ $this->message = $id .' where '. $arg['class'] .' expected';
+ return false;
+ }
+
+ if (isset($arg['post-call']) &&
+ !call_user_func(array(&$this, $arg['post-call'])))
+ {
+ return false;
+ }
+ array_shift($this->s_['arguments']);
+ }
+
+ $this->message = 'unexpected '. $id;
+ return false;
+ }
+
+ function startStringList($line)
+ {
+ if (!$this->validClass_('string', 'string'))
+ {
+ $this->message = 'line '. $line .': '. $this->message;
+ return false;
+ }
+ else if (!isset($this->s_['arguments'][0]['list']))
+ {
+ $this->message = 'line '. $line .': '. 'left bracket where '. $this->s_['arguments'][0]['class'] .' expected';
+ return false;
+ }
+
+ $this->s_['arguments'][0]['occurrences'] = '+';
+ return true;
+ }
+
+ function endStringList()
+ {
+ array_shift($this->s_['arguments']);
+ }
+
+ function validToken($class, &$text, &$line)
+ {
+ $name = $class . ($class != $text ? " $text" : '');
+
+ // Check if the command needs to be required
+ // TODO: move this to somewhere more appropriate
+ global $requires_;
+ if (isset($this->s_['requires']) &&
+ !in_array('"'.$this->s_['requires'].'"', $requires_))
+ {
+ $this->message = 'line '. $line .': missing require for '. $this->command_;
+ return false;
+ }
+
+ // Make sure the argument has a valid class
+ if (!$this->validClass_($class, $name))
+ {
+ $this->message = 'line '. $line .': '. $this->message;
+ return false;
+ }
+
+ $arg = &$this->s_['arguments'][0];
+ foreach ($arg['values'] as $val)
+ {
+ if (preg_match('/^'. $val['regex'] .'$/m', $text))
+ {
+ // Check if the argument value needs a 'require'
+ if (isset($val['requires']) &&
+ !in_array('"'.$val['requires'].'"', $requires_))
+ {
+ $this->message = 'line '. $line .': missing require for '. $val['name'] .' '. $text;
+ return false;
+ }
+
+ // Check if a possible value of this argument may occur
+ if ($val['occurrences'] == '?' || $val['occurrences'] == '1')
+ {
+ $val['occurrences'] = '0';
+ }
+ else if ($val['occurrences'] == '+')
+ {
+ $val['occurrences'] = '*';
+ }
+ else if ($val['occurrences'] == '0')
+ {
+ $this->message = 'line '. $line .': too many '. $val['name'] .' '. $class .'s near '. $text;
+ return false;
+ }
+
+ // Call extra processing function if defined
+ if (isset($val['call']) && !call_user_func(array(&$this, $val['call']), $text) ||
+ isset($arg['call']) && !call_user_func(array(&$this, $arg['call']), $text))
+ {
+ $this->message = 'line '. $line .': '. $this->message;
+ return false;
+ }
+
+ // Set occurrences appropriately
+ if ($arg['occurrences'] == '?' || $arg['occurrences'] == '1')
+ {
+ array_shift($this->s_['arguments']);
+ }
+ else
+ {
+ $arg['occurrences'] = '*';
+ }
+
+ // Add argument(s) expected to follow right after this one
+ if (isset($val['add']))
+ {
+ while ($add_arg = array_pop($val['add']))
+ {
+ array_unshift($this->s_['arguments'], $add_arg);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ $this->message = 'line '. $line .': unexpected '. $name;
+ return false;
+ }
+
+ function done($class, $text, $line)
+ {
+ if (isset($this->s_['arguments']))
+ {
+ foreach ($this->s_['arguments'] as $arg)
+ {
+ if ($arg['occurrences'] == '+' || $arg['occurrences'] == '1')
+ {
+ $this->message = 'line '. $line .': '. $class .' '. $text .' where '. $arg['class'] .' expected';
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/include/sieve/class_sieve.inc b/include/sieve/class_sieve.inc
--- /dev/null
@@ -0,0 +1,514 @@
+<?
+
+/*
+ * $Id: class_sieve.inc,v 1.1 2005/02/21 09:33:01 cajus Exp $
+ *
+ * Copyright 2001 Dan Ellis <danellis@rushmore.com>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+// TODO before next release: remove ::status() and dependencies
+
+
+define ("F_NO", 0);
+define ("F_OK", 1);
+define ("F_DATA", 2);
+define ("F_HEAD", 3);
+
+define ("EC_NOT_LOGGED_IN", 0);
+define ("EC_QUOTA", 10);
+define ("EC_NOSCRIPTS", 20);
+define ("EC_UNKNOWN", 255);
+/*
+
+SIEVE-PHP.LIB VERSION 0.0.8
+
+(C) 2001 Dan Ellis.
+
+PLEASE READ THE README FILE FOR MORE INFORMATION.
+
+Basically, this is the first re-release. Things are much better than before.
+
+Notes:
+This program/libary has bugs.
+ . This was quickly hacked out, so please let me know what is wrong and if you feel ambitious submit
+ a patch :).
+
+Todo:
+ . Provide better error diagnostics. (mostly done with ver 0.0.5)
+ . Allow other auth mechanisms besides plain (in progress)
+ . Have timing mechanism when port problems arise. (not done yet)
+ . Maybe add the NOOP function. (not done yet)
+ . Other top secret stuff.... (some done, believe me?)
+
+Dan Ellis (danellis@rushmore.com)
+
+This program is released under the GNU Public License.
+
+You should have received a copy of the GNU Public
+ License along with this package; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+See CHANGES for updates since last release
+
+Contributers of patches:
+ Atif Ghaffar
+ Andrew Sterling Hanenkamp <sterling@hanenkamp.com>
+*/
+
+
+class sieve
+{
+ var $host;
+ var $port;
+ var $user;
+ var $pass;
+ var $auth_types; /* a comma seperated list of allowed auth types, in order of preference */
+ var $auth_in_use; /* type of authentication attempted */
+
+ var $line;
+ var $fp;
+ var $retval;
+ var $tmpfile;
+ var $fh;
+ var $len;
+ var $script;
+
+ var $loggedin;
+ var $capabilities;
+ var $error;
+ var $error_raw;
+ var $responses;
+
+ //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN)
+ //so we can decide how to handle certain errors?!?
+
+ //also add a connection type, like PLAIN, MD5, etc...
+
+
+ function get_response()
+ {
+ if($this->loggedin == false or feof($this->fp)){
+ $this->error = EC_NOT_LOGGED_IN;
+ $this->error_raw = "You are not logged in.";
+ return false;
+ }
+
+ unset($this->response);
+ unset($this->error);
+ unset($this->error_raw);
+
+ $this->line=fgets($this->fp,1024);
+ $this->token = split(" ", $this->line, 2);
+
+ if($this->token[0] == "NO"){
+ /* we need to try and extract the error code from here. There are two possibilites: one, that it will take the form of:
+ NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */
+ $this->x = 0;
+ list($this->ltoken, $this->mtoken, $this->rtoken) = split(" ", $this->line." ", 3);
+ if($this->mtoken[0] == "{"){
+ while($this->mtoken[$this->x] != "}" or $this->err_len < 1){
+ $this->err_len = substr($this->mtoken, 1, $this->x);
+ $this->x++;
+ }
+ //print "<br>Trying to receive $this->err_len bytes for result<br>";
+ $this->line = fgets($this->fp,$this->err_len);
+ $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
+ $this->err_recv = strlen($this->line);
+
+ while($this->err_recv < $this->err_len){
+ //print "<br>Trying to receive ".($this->err_len-$this->err_recv)." bytes for result<br>";
+ $this->line = fgets($this->fp, ($this->err_len-$this->err_recv));
+ $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
+ $this->err_recv += strlen($this->line);
+ } /* end while */
+ $this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug...
+ $this->error=EC_UNKNOWN;
+
+ } /* end if */
+ elseif($this->mtoken[0] == "("){
+ switch($this->mtoken){
+ case "(\"QUOTA\")":
+ $this->error = EC_QUOTA;
+ $this->error_raw=$this->rtoken;
+ break;
+ default:
+ $this->error = EC_UNKNOWN;
+ $this->error_raw=$this->rtoken;
+ break;
+ } /* end switch */
+ } /* end elseif */
+ else{
+ $this->error = EC_UNKNOWN;
+ $this->error_raw = $this->line;
+ }
+ return false;
+
+ } /* end if */
+ elseif(substr($this->token[0],0,-2) == "OK"){
+ return true;
+ } /* end elseif */
+ elseif($this->token[0][0] == "{"){
+
+ /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */
+
+ /* the first line is the len field {xx}, which we don't care about at this point */
+ $this->line = fgets($this->fp,1024);
+ while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
+ $this->response[]=$this->line;
+ $this->line = fgets($this->fp, 1024);
+ }
+ if(substr($this->line,0,2) == "OK")
+ return true;
+ else
+ return false;
+ } /* end elseif */
+ elseif($this->token[0][0] == "\""){
+
+ /* I'm going under the _assumption_ that the only function that will get here is the listscripts().
+ I could very well be mistaken here, if I am, this part needs some rework */
+
+ $this->found_script=false;
+
+ while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
+ $this->found_script=true;
+ list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2);
+ //hmmm, a bug in php, if there is no space on explode line, a warning is generated...
+
+ if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){
+ $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1);
+ }
+ else
+ $this->response[] = substr(rtrim($this->ltoken),1,-1);
+ $this->line = fgets($this->fp, 1024);
+ } /* end while */
+
+ return true;
+
+ } /* end elseif */
+ else{
+ $this->error = EC_UNKNOWN;
+ $this->error_raw = $this->line;
+ print "<b><i>UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line</i></b><br>";
+ return false;
+ } /* end else */
+ } /* end get_response() */
+
+ function sieve($host, $port, $user, $pass, $auth="", $auth_types="PLAIN DIGEST-MD5")
+ {
+ $this->host=$host;
+ $this->port=$port;
+ $this->user=$user;
+ $this->pass=$pass;
+ if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */
+ $this->auth = $this->user;
+ else
+ $this->auth = $auth;
+ $this->auth_types=$auth_types; /* Allowed authentication types */
+ $this->fp=0;
+ $this->line="";
+ $this->retval="";
+ $this->tmpfile="";
+ $this->fh=0;
+ $this->len=0;
+ $this->capabilities="";
+ $this->loggedin=false;
+ $this->error= "";
+ $this->error_raw="";
+ }
+
+ function parse_for_quotes($string)
+ {
+ /* This function tokenizes a line of input by quote marks and returns them as an array */
+
+ $start = -1;
+ $index = 0;
+
+ for($ptr = 0; $ptr < strlen($string); $ptr++){
+ if($string[$ptr] == '"' and $string[$ptr] != '\\'){
+ if($start == -1){
+ $start = $ptr;
+ } /* end if */
+ else{
+ $token[$index++] = substr($string, $start + 1, $ptr - $start - 1);
+ $found = true;
+ $start = -1;
+ } /* end else */
+
+ } /* end if */
+
+ } /* end for */
+
+ if(isset($token))
+ return $token;
+ else
+ return false;
+ } /* end function */
+
+ function status($string)
+ {
+ //this should probably be replaced by a smarter parser.
+
+ /* Need to remove this and all dependencies from the class */
+
+ switch (substr($string, 0,2)){
+ case "NO":
+ return F_NO; //there should be some function to extract the error code from this line
+ //NO ("quota") "You are oly allowed x number of scripts"
+ break;
+ case "OK":
+ return F_OK;
+ break;
+ default:
+ switch ($string[0]){
+ case "{":
+ //do parse here for {}'s maybe modify parse_for_quotes to handle any parse delimiter?
+ return F_HEAD;
+ break;
+ default:
+ return F_DATA;
+ break;
+ } /* end switch */
+ } /* end switch */
+ } /* end status() */
+
+ function sieve_login()
+ {
+
+ $this->fp=fsockopen($this->host,$this->port);
+ if($this->fp == false)
+ return false;
+
+ $this->line=fgets($this->fp,1024);
+
+ //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard
+ //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
+ //So, if we see IMLEMENTATION in the first line, then we are done.
+
+ if(ereg("IMPLEMENTATION",$this->line))
+ {
+ //we're on the Cyrus V2 sieve server
+ while(sieve::status($this->line) == F_DATA){
+
+ $this->item = sieve::parse_for_quotes($this->line);
+
+ if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
+ $this->capabilities["implementation"] = $this->item[1];
+
+ elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
+
+ if(strcmp($this->item[0], "SIEVE") == 0)
+ $this->cap_type="modules";
+ else
+ $this->cap_type="auth";
+
+ $this->modules = split(" ", $this->item[1]);
+ if(is_array($this->modules)){
+ foreach($this->modules as $this->module)
+ $this->capabilities[$this->cap_type][$this->module]=true;
+ } /* end if */
+ elseif(is_string($this->modules))
+ $this->capabilites[$this->cap_type][$this->modules]=true;
+ }
+ else{
+ $this->capabilities["unknown"][]=$this->line;
+ }
+ $this->line=fgets($this->fp,1024);
+
+ }// end while
+ }
+ else
+ {
+ //we're on the older Cyrus V1. server
+ //this version does not support module reporting. We only have auth types.
+ $this->cap_type="auth";
+
+ //break apart at the "Cyrus timsieve...." "SASL={......}"
+ $this->item = sieve::parse_for_quotes($this->line);
+
+ $this->capabilities["implementation"] = $this->item[0];
+
+ //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz}
+ $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
+
+ //then split again at the ", " stuff.
+ $this->modules = split($this->modules, ", ");
+
+ //fill up our $this->modules property
+ if(is_array($this->modules)){
+ foreach($this->modules as $this->module)
+ $this->capabilities[$this->cap_type][$this->module]=true;
+ } /* end if */
+ elseif(is_string($this->modules))
+ $this->capabilites[$this->cap_type][$this->module]=true;
+ }
+
+
+
+
+ if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes?
+ $this->error=EC_UNKNOWN;
+ $this->error_raw = "Server not allowing connections.";
+ return false;
+ }
+
+ /* decision login to decide what type of authentication to use... */
+
+ /* Loop through each allowed authentication type and see if the server allows the type */
+ foreach(split(" ",$this->auth_types) as $auth_type)
+ {
+ if ($this->capabilities["auth"][$auth_type])
+ {
+ /* We found an auth type that is allowed. */
+ $this->auth_in_use = $auth_type;
+ break;
+ }
+ }
+
+ /* Fill error message if no auth types are present */
+ if (!isset($this->capabilities["auth"])){
+ $this->error=EC_UNKNOWN;
+ $this->error_raw = "No authentication methods found - please check your sieve setup for missing sasl modules";
+ return false;
+ }
+
+ /* call our authentication program */
+ return sieve::authenticate();
+
+ }
+
+ function sieve_logout()
+ {
+ if($this->loggedin==false)
+ return false;
+
+ fputs($this->fp,"LOGOUT\r\n");
+ fclose($this->fp);
+ $this->loggedin=false;
+ return true;
+ }
+
+ function sieve_sendscript($scriptname, $script)
+ {
+ if($this->loggedin==false)
+ return false;
+ $this->script=stripslashes($script);
+ $len=strlen($this->script);
+ fputs($this->fp, "PUTSCRIPT \"$scriptname\" {".$len."+}\r\n");
+ fputs($this->fp, "$this->script\r\n");
+
+ return sieve::get_response();
+
+ }
+
+ //it appears the timsieved does not honor the NUMBER type. see lex.c in timsieved src.
+ //don't expect this function to work yet. I might have messed something up here, too.
+ function sieve_havespace($scriptname, $scriptsize)
+ {
+ if($this->loggedin==false)
+ return false;
+ fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
+ return sieve::get_response();
+
+ }
+
+ function sieve_setactivescript($scriptname)
+ {
+ if($this->loggedin==false)
+ return false;
+
+ fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");
+ return sieve::get_response();
+
+ }
+
+ function sieve_getscript($scriptname)
+ {
+ unset($this->script);
+ if($this->loggedin==false)
+ return false;
+
+ fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
+ return sieve::get_response();
+
+ }
+
+
+ function sieve_deletescript($scriptname)
+ {
+ if($this->loggedin==false)
+ return false;
+
+ fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");
+
+ return sieve::get_response();
+ }
+
+
+ function sieve_listscripts()
+ {
+ fputs($this->fp, "LISTSCRIPTS\r\n");
+ sieve::get_response(); //should always return true, even if there are no scripts...
+ if(isset($this->found_script) and $this->found_script)
+ return true;
+ else{
+ $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found...
+ $this->error_raw="No scripts found for this account.";
+ return false;
+ }
+ }
+
+ function sieve_alive()
+ {
+ if(!isset($this->fp) or $this->fp==0){
+ $this->error = EC_NOT_LOGGED_IN;
+ return false;
+ }
+ elseif(feof($this->fp)){
+ $this->error = EC_NOT_LOGGED_IN;
+ return false;
+ }
+ else
+ return true;
+ }
+
+ function authenticate()
+ {
+
+ switch ($this->auth_in_use) {
+
+ case "PLAIN":
+ $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
+
+ $this->len=strlen($auth);
+ fputs($this->fp, "AUTHENTICATE \"PLAIN\" {".$this->len."+}\r\n");
+ fputs($this->fp, "$auth\r\n");
+
+ $this->line=fgets($this->fp,1024);
+ while(sieve::status($this->line) == F_DATA)
+ $this->line=fgets($this->fp,1024);
+
+ if(sieve::status($this->line) == F_NO)
+ return false;
+ $this->loggedin=true;
+ return true;
+ break;
+
+ default:
+ return false;
+ break;
+
+ }//end switch
+
+
+ }//end authenticate()
+
+
+}
+
+
+
+?>
diff --git a/include/sieve/class_sievescript.php b/include/sieve/class_sievescript.php
--- /dev/null
@@ -0,0 +1,321 @@
+<?php
+
+/*
+ requirestring: envelope, fileinto, reject, comparator-*
+ address-part: localpart, domain, all*
+ match-type: is*, contains, matches
+ comperator: i;octet, i;ascii-casemap
+
+ <if> <test:+> <block:1>
+ <elsif> <test:+> <block:1>
+ <else> <block:1>
+ <require> <requirestring:+>
+ <reject> <string:?>
+ <fileinto> <string:1>
+ <redirect> <string:1>
+ <stop>
+ <keep>
+ <discard>
+
+ <address> <address-part,comperator,match-type:?> <string:+> <string:+>
+ <envelope> <address-part,comperator,match-type:?> <string:+> <string:+>
+ <header> <comperator,match-type:?> <string:+> <string:+>
+ <size> <:over|:under:1> <number:1>
+ <allof> <test:+>
+ <anyof> <test:+>
+ <exists> <string:+>
+ <not> <test:1>
+ <true>
+ <false>
+*/
+
+class SieveScript
+{
+ var $_scanner;
+ var $_script;
+ var $_tree;
+ var $_status;
+ var $_noMoreRequires;
+
+ var $_test_identifier = array('address', 'allof', 'anyof', 'envelope', 'exists', 'false', 'header', 'not', 'size', 'true');
+
+ var $status_text;
+
+ function parse($script)
+ {
+ $this->status_text = "incomplete";
+
+ $root = 'script_start';
+ $this->_noMoreRequires = false;
+ $this->_script = $script;
+ $this->_tree = new Tree($root);
+ $this->_tree->setDumpFunc(array($this, '_dumpToken'));
+ $this->_scanner = new Scanner($this->_script);
+ $this->_scanner->setCommentFunc(array($this, '_comment'));
+
+ if ($this->_commands($this->_tree->getRoot()) &&
+ $this->_scanner->nextTokenIs('script-end'))
+ {
+ return $this->_success('success');
+ }
+
+ return $this->_status;
+ }
+
+ function _dumpToken(&$token)
+ {
+ if (is_array($token))
+ {
+ $str = "<" . chop(mb_substr($this->_script, $token['pos'], $token['len'])) . "> ";
+ foreach ($token as $k => $v)
+ {
+ $str .= " $k:$v";
+ }
+ return $str;
+ }
+
+ return strval($token);
+ }
+
+ function _tokenStringIs($token, $text)
+ {
+ return mb_substr($this->_script, $token['pos'], $token['len']) == $text;
+ }
+
+ function _success($text = null)
+ {
+ if ($text != null)
+ {
+ $this->status_text = $text;
+ }
+
+ return $this->_status = true;
+ }
+
+ function _error($text, $token = null)
+ {
+ if ($token != null)
+ {
+ $text = 'line '. $token['line'] .': '. $token['class'] . " where $text expected near ".
+ '"'. mb_substr($this->_script, $token['pos'], $token['len']) .'"';
+ }
+
+ $this->status_text = $text;
+ return $this->_status = false;
+ }
+
+ function _done()
+ {
+ return false;
+ }
+
+ function _comment($token)
+ {
+ $this->_tree->addChild($token);
+ }
+
+ function _commands($parent_id)
+ {
+ while ($this->_command($parent_id));
+
+ return $this->_status;
+ }
+
+ function _command($parent_id)
+ {
+ if (!$this->_scanner->nextTokenIs('identifier'))
+ {
+ if ($this->_scanner->nextTokenIs(array('right-curly', 'script-end')))
+ {
+ return $this->_done();
+ }
+ return $this->_error('identifier', $this->_scanner->peekNextToken());
+ }
+
+ // Get and check a command token
+ $token = $this->_scanner->nextToken();
+ $command = mb_substr($this->_script, $token['pos'], $token['len']);
+
+ if (!in_array($command, array('if', 'elsif', 'else', 'require', 'stop', 'reject', 'fileinto', 'redirect', 'keep', 'discard')))
+ {
+ return $this->_error('unknown command: '. $command);
+ }
+
+ if ($command != 'require')
+ {
+ $this->_noMoreRequires = true;
+ }
+ else if ($this->_noMoreRequires)
+ {
+ return $this->_error('misplaced require');
+ }
+
+ $this_node = $this->_tree->addChildTo($parent_id, $token);
+
+ if (in_array($command, array('if', 'elsif', 'require', 'reject', 'fileinto', 'redirect')))
+ {
+ // TODO: handle optional arguments in reject
+ if ($this->_arguments($this_node) == false)
+ {
+ return false;
+ }
+ }
+
+ $token = $this->_scanner->nextToken();
+ if (in_array($command, array('if', 'elsif', 'else')))
+ {
+ if ($token['class'] != 'left-curly')
+ {
+ return $this->_error('block', $token);
+ }
+ $this->_tree->addChildTo($this_node, $token);
+ return $this->_block($this_node);
+ }
+ else if ($token['class'] != 'semicolon')
+ {
+ return $this->_error('semicolon', $token);
+ }
+
+ $this->_tree->addChildTo($this_node, $token);
+ return $this->_success();
+ }
+
+ function _block($parent_id)
+ {
+ //TODO: test if cmd is ok w/ block
+ if ($this->_commands($parent_id))
+ {
+ $token = $this->_scanner->nextToken();
+
+ if ($token['class'] != 'right-curly')
+ {
+ return $this->_error('closing curly brace', $token);
+ }
+
+ $this->_tree->addChildTo($parent_id, $token);
+ return $this->_success();
+ }
+ return $this->_status;
+ }
+
+ function _arguments($parent_id)
+ {
+ while ($this->_argument($parent_id));
+ if ($this->_status == true)
+ {
+ $this->_testlist($parent_id);
+ }
+ return $this->_status;
+ }
+
+ function _argument($parent_id)
+ {
+ if (!$this->_scanner->nextTokenIs(array('number', 'tag')))
+ {
+ return $this->_stringlist($parent_id);
+ }
+ else
+ {
+ if ($this->_tokenStringIs($this->_tree->getNode($parent_id), 'require'))
+ {
+ return $this->_error('stringlist', $this->_scanner->nextToken());
+ }
+ }
+
+ $token = $this->_scanner->nextToken();
+ $this->_tree->addChildTo($parent_id, $token);
+
+ return $this->_success();
+ }
+
+ function _test($parent_id)
+ {
+ if (!$this->_scanner->nextTokenIs('identifier'))
+ {
+ return $this->_done();
+ }
+
+ $token = $this->_scanner->nextToken();
+ $this_node = $this->_tree->addChildTo($parent_id, $token);
+
+ $this->_arguments($this_node);
+
+ //TODO: check test for validity here
+
+ return $this->_status;
+ }
+
+ function _testlist($parent_id)
+ {
+ if (!$this->_scanner->nextTokenIs('left-parant'))
+ {
+ return $this->_test($parent_id);
+ }
+
+ $token = $this->_scanner->nextToken();
+ $this->_tree->addChildTo($parent_id, $token);
+
+ while ($token['class'] != 'right-parant')
+ {
+ if (!$this->_test($parent_id))
+ {
+ return $this->_status;
+ }
+
+ $token = $this->_scanner->nextToken();
+
+ if ($token['class'] != 'comma' && $token['class'] != 'right-parant')
+ {
+ return $this->_error('comma or closing paranthesis', $token);
+ }
+
+ $this->_tree->addChildTo($parent_id, $token);
+ }
+
+ return $this->_success();
+ }
+
+ function _string($parent_id)
+ {
+ if (!$this->_scanner->nextTokenIs(array('quoted-string', 'multi-line')))
+ {
+ return $this->_done();
+ }
+
+ $this->_tree->addChildTo($parent_id, $this->_scanner->nextToken());
+
+ return $this->_success();
+ }
+
+ function _stringlist($parent_id)
+ {
+ if (!$this->_scanner->nextTokenIs('left-bracket'))
+ {
+ return $this->_string($parent_id);
+ }
+
+ $token = $this->_scanner->nextToken();
+ $this->_tree->addChildTo($parent_id, $token);
+
+ while ($token['class'] != 'right-bracket')
+ {
+ if (!$this->_string($parent_id))
+ {
+ return $this->_status;
+ }
+
+ $token = $this->_scanner->nextToken();
+
+ if ($token['class'] != 'comma' && $token['class'] != 'right-bracket')
+ {
+ return $this->_error('comma or closing bracket', $token);
+ }
+
+ $this->_tree->addChildTo($parent_id, $token);
+ }
+
+ return $this->_success();
+ }
+}
+
+?>
diff --git a/include/sieve/class_tree.inc b/include/sieve/class_tree.inc
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+
+class Tree
+{
+ var $childs_;
+ var $parents_;
+ var $nodes_;
+ var $maxId_;
+ var $dumpFn_;
+ var $dump_;
+
+ function Tree(&$root)
+ {
+ $this->_construct($root);
+ }
+
+ function _construct(&$root)
+ {
+ $this->childs_ = array();
+ $this->parents_ = array();
+ $this->nodes_ = array();
+ $this->maxId_ = 0;
+
+ $this->parents_[0] = null;
+ $this->nodes_[0] = $root;
+ }
+
+ function addChild(&$child)
+ {
+ return $this->addChildTo($this->maxId_, $child);
+ }
+
+ function addChildTo($parent_id, &$child)
+ {
+ if (!is_int($parent_id) ||
+ !isset($this->nodes_[$parent_id]))
+ {
+ return null;
+ }
+
+ if (!isset($this->childs_[$parent_id]))
+ {
+ $this->childs_[$parent_id] = array();
+ }
+
+ $child_id = ++$this->maxId_;
+ $this->nodes_[$child_id] = $child;
+ $this->parents_[$child_id] = $parent_id;
+ array_push($this->childs_[$parent_id], $child_id);
+
+ return $child_id;
+ }
+
+ function getRoot()
+ {
+ if (!isset($this->nodes_[0]))
+ {
+ return null;
+ }
+
+ return 0;
+ }
+
+ function getParent($node_id)
+ {
+ if (!is_int($node_id) ||
+ !isset($this->nodes_[$node_id]))
+ {
+ return null;
+ }
+
+ return $this->parents_[$node_id];
+ }
+
+ function getChilds($node_id)
+ {
+ if (!is_int($node_id) ||
+ !isset($this->nodes_[$node_id]))
+ {
+ return null;
+ }
+
+ if (!isset($this->childs_[$node_id]))
+ {
+ return array();
+ }
+
+ return $this->childs_[$node_id];
+ }
+
+ function getNode($node_id)
+ {
+ if (!is_int($node_id) ||
+ !isset($this->nodes_[$node_id]))
+ {
+ return null;
+ }
+
+ return $this->nodes_[$node_id];
+ }
+
+ function getLastNode($parent_id)
+ {
+ $childs = $this->getChilds($parent_id);
+
+ for ($i=count($childs); $i>0; --$i)
+ {
+ $node = $this->getNode($childs[$i-1]);
+ if ($node['text'] == '{')
+ {
+ // return command owning the block
+ return $this->getNode($parent_id);
+ }
+ if ($node['class'] != 'comment')
+ {
+ return $node;
+ }
+ }
+
+ return $this->getNode($parent_id);
+ }
+
+ function setDumpFunc($callback)
+ {
+ if ($callback == NULL || is_callable($callback))
+ {
+ $this->dumpFn_ = $callback;
+ }
+ }
+
+ function dump()
+ {
+ $this->dump_ = "tree\n";
+ $this->doDump_(0, '', true);
+ return $this->dump_;
+ }
+
+ function doDump_($node_id, $prefix, $last)
+ {
+ if ($last)
+ {
+ $infix = '`--- ';
+ $c_prefix = $prefix . ' ';
+ }
+ else
+ {
+ $infix = '|--- ';
+ $c_prefix = $prefix . '| ';
+ }
+
+ $node = $this->nodes_[$node_id];
+ if ($this->dumpFn_ != NULL)
+ {
+ $this->dump_ .= $prefix . $infix . call_user_func($this->dumpFn_, $node) . "\n";
+ }
+ else
+ {
+ $this->dump_ .= "$prefix$infix$node\n";
+ }
+
+ $childs = $this->childs_[$node_id];
+ for ($i=0; $i<count($childs); ++$i)
+ {
+ $c_last = false;
+ if ($i+1 == count($childs))
+ {
+ $c_last = true;
+ }
+
+ $this->doDump_($childs[$i], $c_prefix, $c_last);
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/include/sieve/libsieve.php b/include/sieve/libsieve.php
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+require_once 'class_parser.inc';
+require_once 'class_scanner.inc';
+require_once 'class_semantics.inc';
+require_once 'class_tree.inc';
+require_once 'class_sieve.inc';
+?>
diff --git a/plugins/personal/mail/class_mailAccount.inc b/plugins/personal/mail/class_mailAccount.inc
index 468e4ab7e6544ad68f6a08f5f641013b8e671c99..9dcd068225cfef119a4dbbd2907c104b45bbdc78 100644 (file)
*/
/* Load sieve support */
-require_once ("class_sieve.inc");
+require_once ("sieve/libsieve.php");
/* Load mail methods */
global $BASE_DIR;