1<?php
2
3require_once "parser.php";
4require_once "tokenizer.php";
5require_once "exceptions.php";
6
7use \AST\ElementDefinition;
8use \AST\TokenDefinition;
9use \AST\InvalidExpressionException;
10use \AST\MalformedExpressionException;
11use \AST\Fixing;
12
13
14class EvaluationContext {
15    public $SIMULATE_IN_GROUPS = null;
16    public $SIMULATE_USERS = null;
17
18    public function belongToGroup($group) {
19        if (is_array($this->SIMULATE_IN_GROUPS)) {
20            if (in_array($group, $this->SIMULATE_IN_GROUPS)) {
21                return true;
22            }
23        }
24        global $INFO;
25        $key1 = 'userinfo';
26        $key2 = 'grps';
27        if (is_array($INFO) && array_key_exists($key1, $INFO)) {
28            if (is_array($INFO[$key1]) && array_key_exists($key2, $INFO[$key1])) {
29                return in_array($group, $INFO[$key1][$key2]);
30            }
31        }
32        return false;
33    }
34
35    public function isUser($user) {
36        if (is_array($this->SIMULATE_USERS)) {
37            if (in_array($user, $this->SIMULATE_USERS)) {
38                return true;
39            }
40        }
41        $key = 'REMOTE_USER';
42        if (array_key_exists($key, $_SERVER)) {
43            return $_SERVER[$key] == $user;
44        }
45        return false;
46    }
47}
48
49function auth_expr_evaluation_context() {
50    static $ctx = null;
51    if ($ctx === null) {
52        $ctx = new EvaluationContext();
53    }
54    return $ctx;
55}
56
57
58class Literal extends ElementDefinition {
59    public function __construct() {
60        $T_LITERAL = new TokenDefinition(null, 'LIT', '/[\w.-]+|".+?(?<!\\\\)(\\\\\\\\)*"/');
61        parent::__construct('Literal', Fixing::None, $T_LITERAL, 0);
62    }
63    public static function unquote($strValue) {
64        // No multibyte variant, here we just operate on bytes
65        $strValue = str_replace('\\\\', '\\', $strValue);
66        $strValue = str_replace('\\"', '"', $strValue);
67        $strValue = substr($strValue, 1, strlen($strValue) - 2);
68        return $strValue;
69    }
70    public static function isQuoted($strValue) {
71        if (TokenDefinition::supportsMultibyte()) {
72            return mb_strlen($strValue) > 0 && mb_substr($strValue, 0, 1) == '"';
73        } else {
74            return strlen($strValue) > 0 && substr($strValue, 0, 1) == '"';
75        }
76    }
77    public static function getInstanceLiteralValue($elmInstance) {
78        $strValue = $elmInstance->getStringValue();
79        if (self::isQuoted($strValue)) {
80            return self::unquote($strValue);
81        }
82        return $strValue;
83    }
84    public function _evaluateWellFormed($elmInstance) {
85        $userName = self::getInstanceLiteralValue($elmInstance);
86        return auth_expr_evaluation_context()->isUser($userName);
87    }
88}
89
90class SubExpr extends ElementDefinition {
91    public function __construct() {
92        $T_OPEN_PAREN = new TokenDefinition('(', 'OPENP');
93        $T_CLOSE_PAREN = new TokenDefinition(')', 'CLOSEP');
94        parent::__construct('SubExpr', Fixing::Wrap, array($T_OPEN_PAREN, $T_CLOSE_PAREN), 1, null, true);
95    }
96    public function ensureWellFormed($elmInstance) {
97        parent::ensureWellFormed($elmInstance);
98        if (count($elmInstance->args()) != 1) {
99            throw new MalformedExpressionException($elmInstance, 'A subexpression must have exactly one root');
100        }
101    }
102    public function _evaluateWellFormed($elmInstance) {
103        return $elmInstance->evaluateArgs()[0];
104    }
105}
106
107class OpInGroup extends ElementDefinition {
108    public function __construct() {
109        $T_AT = new TokenDefinition('@', 'AT');
110        parent::__construct('InGroup', Fixing::Prefix, $T_AT, 2);
111    }
112    public function ensureWellFormed($elmInstance) {
113        parent::ensureWellFormed($elmInstance);
114        if (!($elmInstance->args()[0]->definition() instanceof Literal)) {
115            throw new MalformedExpressionException($elmInstance, 'A in-group operator <@> must take exactly one literal argument.');
116        }
117    }
118    public function _evaluateWellFormed($elmInstance) {
119        $literalInstance = $elmInstance->args()[0];
120        $groupName = Literal::getInstanceLiteralValue($literalInstance);
121        return auth_expr_evaluation_context()->belongToGroup($groupName);
122    }
123}
124
125class OpNot extends ElementDefinition {
126    public function __construct() {
127        $T_EXCL = new TokenDefinition('!', 'EXCL');
128        parent::__construct('Not', Fixing::Prefix, $T_EXCL, 3);
129    }
130    public function _evaluateWellFormed($elmInstance) {
131        $argValues = $elmInstance->evaluateArgs();
132        if (!is_bool($argValues[0])) {
133            throw new InvalidExpressionException($elmInstance, 'Not called on a non-boolean argument.');
134        }
135        return !$argValues[0];
136    }
137}
138
139class OpAnd extends ElementDefinition {
140    public function __construct() {
141        $T_AND = new TokenDefinition('&&', 'AND');
142        parent::__construct('And', Fixing::Infix, $T_AND, 4);
143    }
144    public function _evaluateWellFormed($elmInstance) {
145        $argValues = $elmInstance->evaluateArgs();
146        foreach ($argValues as $arg) {
147            if (!is_bool($arg)) {
148                throw new InvalidExpressionException($elmInstance, 'And called on non-boolean arguments.');
149            }
150            if (!$arg) {
151                return false;
152            }
153        }
154        return true;
155    }
156}
157
158class OpOr extends ElementDefinition {
159    public function __construct() {
160        $T_OR = new TokenDefinition('||', 'OR', '/(\|\||,)/');
161        parent::__construct('Or', Fixing::Infix, $T_OR, 5);
162    }
163    public function _evaluateWellFormed($elmInstance) {
164        $argValues = $elmInstance->evaluateArgs();
165        foreach ($argValues as $arg) {
166            if (!is_bool($arg)) {
167                throw new InvalidExpressionException($elmInstance, 'Or called on non-boolean arguments.');
168            }
169            if ($arg) {
170                return true;
171            }
172        }
173        return false;
174    }
175}
176
177function auth_expr_all_elements() {
178    static $ALL_ELEMENTS = null;
179    if ($ALL_ELEMENTS === null) {
180        $ALL_ELEMENTS = array(new Literal(), new SubExpr(), new OpInGroup(), new OpNot(), new OpAnd(), new OpOr());
181    }
182    return $ALL_ELEMENTS;
183}
184
185function auth_expr_ignore_tokens() {
186    static $IGNORE_TOKENS = null;
187    if ($IGNORE_TOKENS === null) {
188        $IGNORE_TOKENS = array(new TokenDefinition(' ', 'SPC', '/\s+/'));
189    }
190    return $IGNORE_TOKENS;
191}
192
193function auth_expr_all_tokens() {
194    static $ALL_TOKENS = null;
195    if ($ALL_TOKENS === null) {
196        $ALL_TOKENS = array_merge(auth_expr_ignore_tokens(), ElementDefinition::extractUsedTokens(auth_expr_all_elements()));
197    }
198    return $ALL_TOKENS;
199}
200
201function auth_expr_parse($expr) {
202    return \AST\parse(\AST\tokenize($expr, auth_expr_all_tokens(), auth_expr_ignore_tokens()), auth_expr_all_elements());
203}
204
205?>