Skip to main content

actix_security_core/http/security/expression/
parser.rs

1//! Security expression parser.
2//!
3//! Parses Spring Security-like expressions into an AST.
4
5use std::fmt;
6use std::iter::Peekable;
7use std::str::Chars;
8
9use super::ast::{BinaryOp, Expression, UnaryOp};
10
11/// Error type for expression parsing.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ParseError {
14    /// Unexpected end of input
15    UnexpectedEof,
16    /// Unexpected character
17    UnexpectedChar(char),
18    /// Unexpected token
19    UnexpectedToken(String),
20    /// Unclosed parenthesis
21    UnclosedParen,
22    /// Unclosed string
23    UnclosedString,
24    /// Empty expression
25    EmptyExpression,
26    /// Invalid function call
27    InvalidFunction(String),
28}
29
30impl fmt::Display for ParseError {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            ParseError::UnexpectedEof => write!(f, "unexpected end of expression"),
34            ParseError::UnexpectedChar(c) => write!(f, "unexpected character: '{}'", c),
35            ParseError::UnexpectedToken(t) => write!(f, "unexpected token: '{}'", t),
36            ParseError::UnclosedParen => write!(f, "unclosed parenthesis"),
37            ParseError::UnclosedString => write!(f, "unclosed string literal"),
38            ParseError::EmptyExpression => write!(f, "empty expression"),
39            ParseError::InvalidFunction(name) => write!(f, "invalid function: '{}'", name),
40        }
41    }
42}
43
44impl std::error::Error for ParseError {}
45
46/// Token types for the lexer.
47#[derive(Debug, Clone, PartialEq, Eq)]
48enum Token {
49    /// Identifier (function name, keyword)
50    Ident(String),
51    /// String literal
52    String(String),
53    /// Left parenthesis
54    LParen,
55    /// Right parenthesis
56    RParen,
57    /// Comma
58    Comma,
59    /// AND operator
60    And,
61    /// OR operator
62    Or,
63    /// NOT operator
64    Not,
65    /// Boolean true
66    True,
67    /// Boolean false
68    False,
69}
70
71/// A parsed security expression.
72///
73/// # Example
74/// ```ignore
75/// use actix_security_core::http::security::expression::SecurityExpression;
76///
77/// let expr = SecurityExpression::parse("hasRole('ADMIN') OR hasAuthority('write')")?;
78/// let result = expr.evaluate(&user);
79/// ```
80#[derive(Debug, Clone)]
81pub struct SecurityExpression {
82    /// The original expression string
83    source: String,
84    /// The parsed AST
85    ast: Expression,
86}
87
88impl SecurityExpression {
89    /// Parses a security expression string.
90    ///
91    /// # Arguments
92    /// * `expr` - The expression string to parse
93    ///
94    /// # Returns
95    /// A parsed `SecurityExpression` or a `ParseError`
96    ///
97    /// # Example
98    /// ```ignore
99    /// let expr = SecurityExpression::parse("hasRole('ADMIN')")?;
100    /// ```
101    pub fn parse(expr: &str) -> Result<Self, ParseError> {
102        let tokens = tokenize(expr)?;
103        if tokens.is_empty() {
104            return Err(ParseError::EmptyExpression);
105        }
106
107        let ast = Parser::new(tokens).parse()?;
108
109        Ok(SecurityExpression {
110            source: expr.to_string(),
111            ast,
112        })
113    }
114
115    /// Returns the original expression string.
116    pub fn source(&self) -> &str {
117        &self.source
118    }
119
120    /// Returns a reference to the parsed AST.
121    pub fn ast(&self) -> &Expression {
122        &self.ast
123    }
124
125    /// Consumes self and returns the AST.
126    pub fn into_ast(self) -> Expression {
127        self.ast
128    }
129}
130
131/// Tokenizes an expression string into tokens.
132fn tokenize(expr: &str) -> Result<Vec<Token>, ParseError> {
133    let mut tokens = Vec::new();
134    let mut chars = expr.chars().peekable();
135
136    while let Some(&c) = chars.peek() {
137        match c {
138            // Whitespace - skip
139            ' ' | '\t' | '\n' | '\r' => {
140                chars.next();
141            }
142
143            // Parentheses
144            '(' => {
145                chars.next();
146                tokens.push(Token::LParen);
147            }
148            ')' => {
149                chars.next();
150                tokens.push(Token::RParen);
151            }
152
153            // Comma
154            ',' => {
155                chars.next();
156                tokens.push(Token::Comma);
157            }
158
159            // String literals (single or double quotes)
160            '\'' | '"' => {
161                tokens.push(parse_string(&mut chars)?);
162            }
163
164            // Operators
165            '&' => {
166                chars.next();
167                if chars.peek() == Some(&'&') {
168                    chars.next();
169                    tokens.push(Token::And);
170                } else {
171                    return Err(ParseError::UnexpectedChar('&'));
172                }
173            }
174            '|' => {
175                chars.next();
176                if chars.peek() == Some(&'|') {
177                    chars.next();
178                    tokens.push(Token::Or);
179                } else {
180                    return Err(ParseError::UnexpectedChar('|'));
181                }
182            }
183            '!' => {
184                chars.next();
185                tokens.push(Token::Not);
186            }
187
188            // Identifiers and keywords
189            'a'..='z' | 'A'..='Z' | '_' => {
190                tokens.push(parse_identifier(&mut chars));
191            }
192
193            // Unknown character
194            _ => {
195                return Err(ParseError::UnexpectedChar(c));
196            }
197        }
198    }
199
200    Ok(tokens)
201}
202
203/// Parses a string literal.
204fn parse_string(chars: &mut Peekable<Chars>) -> Result<Token, ParseError> {
205    let quote = chars.next().unwrap(); // ' or "
206    let mut value = String::new();
207
208    loop {
209        match chars.next() {
210            Some(c) if c == quote => {
211                return Ok(Token::String(value));
212            }
213            Some('\\') => {
214                // Escape sequence
215                if let Some(escaped) = chars.next() {
216                    value.push(escaped);
217                } else {
218                    return Err(ParseError::UnclosedString);
219                }
220            }
221            Some(c) => {
222                value.push(c);
223            }
224            None => {
225                return Err(ParseError::UnclosedString);
226            }
227        }
228    }
229}
230
231/// Parses an identifier or keyword.
232fn parse_identifier(chars: &mut Peekable<Chars>) -> Token {
233    let mut ident = String::new();
234
235    while let Some(&c) = chars.peek() {
236        if c.is_alphanumeric() || c == '_' {
237            ident.push(c);
238            chars.next();
239        } else {
240            break;
241        }
242    }
243
244    // Check for keywords
245    match ident.to_lowercase().as_str() {
246        "and" => Token::And,
247        "or" => Token::Or,
248        "not" => Token::Not,
249        "true" => Token::True,
250        "false" => Token::False,
251        _ => Token::Ident(ident),
252    }
253}
254
255/// Recursive descent parser for security expressions.
256struct Parser {
257    tokens: Vec<Token>,
258    pos: usize,
259}
260
261impl Parser {
262    fn new(tokens: Vec<Token>) -> Self {
263        Parser { tokens, pos: 0 }
264    }
265
266    fn parse(&mut self) -> Result<Expression, ParseError> {
267        let expr = self.parse_or()?;
268
269        if self.pos < self.tokens.len() {
270            return Err(ParseError::UnexpectedToken(format!(
271                "{:?}",
272                self.tokens[self.pos]
273            )));
274        }
275
276        Ok(expr)
277    }
278
279    fn peek(&self) -> Option<&Token> {
280        self.tokens.get(self.pos)
281    }
282
283    fn advance(&mut self) -> Option<&Token> {
284        let token = self.tokens.get(self.pos);
285        self.pos += 1;
286        token
287    }
288
289    /// Parse OR expressions (lowest precedence)
290    fn parse_or(&mut self) -> Result<Expression, ParseError> {
291        let mut left = self.parse_and()?;
292
293        while matches!(self.peek(), Some(Token::Or)) {
294            self.advance();
295            let right = self.parse_and()?;
296            left = Expression::Binary {
297                left: Box::new(left),
298                op: BinaryOp::Or,
299                right: Box::new(right),
300            };
301        }
302
303        Ok(left)
304    }
305
306    /// Parse AND expressions (higher precedence than OR)
307    fn parse_and(&mut self) -> Result<Expression, ParseError> {
308        let mut left = self.parse_unary()?;
309
310        while matches!(self.peek(), Some(Token::And)) {
311            self.advance();
312            let right = self.parse_unary()?;
313            left = Expression::Binary {
314                left: Box::new(left),
315                op: BinaryOp::And,
316                right: Box::new(right),
317            };
318        }
319
320        Ok(left)
321    }
322
323    /// Parse unary expressions (NOT)
324    fn parse_unary(&mut self) -> Result<Expression, ParseError> {
325        if matches!(self.peek(), Some(Token::Not)) {
326            self.advance();
327            let expr = self.parse_unary()?;
328            return Ok(Expression::Unary {
329                op: UnaryOp::Not,
330                expr: Box::new(expr),
331            });
332        }
333
334        self.parse_primary()
335    }
336
337    /// Parse primary expressions (functions, booleans, groups)
338    fn parse_primary(&mut self) -> Result<Expression, ParseError> {
339        match self.peek().cloned() {
340            Some(Token::True) => {
341                self.advance();
342                Ok(Expression::Boolean(true))
343            }
344            Some(Token::False) => {
345                self.advance();
346                Ok(Expression::Boolean(false))
347            }
348            Some(Token::LParen) => {
349                self.advance();
350                let expr = self.parse_or()?;
351                if !matches!(self.peek(), Some(Token::RParen)) {
352                    return Err(ParseError::UnclosedParen);
353                }
354                self.advance();
355                Ok(Expression::Group(Box::new(expr)))
356            }
357            Some(Token::Ident(name)) => {
358                self.advance();
359                self.parse_function_call(name)
360            }
361            Some(token) => Err(ParseError::UnexpectedToken(format!("{:?}", token))),
362            None => Err(ParseError::UnexpectedEof),
363        }
364    }
365
366    /// Parse function call arguments
367    fn parse_function_call(&mut self, name: String) -> Result<Expression, ParseError> {
368        // Expect opening parenthesis
369        if !matches!(self.peek(), Some(Token::LParen)) {
370            return Err(ParseError::InvalidFunction(name));
371        }
372        self.advance();
373
374        let mut args = Vec::new();
375
376        // Parse arguments
377        if !matches!(self.peek(), Some(Token::RParen)) {
378            loop {
379                match self.peek().cloned() {
380                    Some(Token::String(s)) => {
381                        self.advance();
382                        args.push(s);
383                    }
384                    Some(Token::Ident(s)) => {
385                        // Allow unquoted identifiers as arguments
386                        self.advance();
387                        args.push(s);
388                    }
389                    Some(token) => {
390                        return Err(ParseError::UnexpectedToken(format!("{:?}", token)));
391                    }
392                    None => {
393                        return Err(ParseError::UnclosedParen);
394                    }
395                }
396
397                // Check for comma or closing paren
398                match self.peek() {
399                    Some(Token::Comma) => {
400                        self.advance();
401                    }
402                    Some(Token::RParen) => break,
403                    Some(token) => {
404                        return Err(ParseError::UnexpectedToken(format!("{:?}", token)));
405                    }
406                    None => {
407                        return Err(ParseError::UnclosedParen);
408                    }
409                }
410            }
411        }
412
413        // Expect closing parenthesis
414        if !matches!(self.peek(), Some(Token::RParen)) {
415            return Err(ParseError::UnclosedParen);
416        }
417        self.advance();
418
419        Ok(Expression::Function { name, args })
420    }
421}