Skip to main content

zentinel_modsec/engine/
phase.rs

1//! Request processing phases.
2
3/// ModSecurity processing phases.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
5#[repr(u8)]
6pub enum Phase {
7    /// Phase 1: Request headers
8    RequestHeaders = 1,
9    /// Phase 2: Request body
10    RequestBody = 2,
11    /// Phase 3: Response headers
12    ResponseHeaders = 3,
13    /// Phase 4: Response body
14    ResponseBody = 4,
15    /// Phase 5: Logging
16    Logging = 5,
17}
18
19impl Phase {
20    /// Every phase, in evaluation order.
21    ///
22    /// A `SecMarker` sits between rules in *all* phases, not just the one the
23    /// rules around it happen to declare, so recording its position requires
24    /// walking each phase in turn.
25    pub const ALL: [Phase; 5] = [
26        Phase::RequestHeaders,
27        Phase::RequestBody,
28        Phase::ResponseHeaders,
29        Phase::ResponseBody,
30        Phase::Logging,
31    ];
32
33    /// Get the phase number.
34    pub fn number(&self) -> u8 {
35        *self as u8
36    }
37
38    /// Get phase name.
39    pub fn name(&self) -> &'static str {
40        match self {
41            Phase::RequestHeaders => "REQUEST_HEADERS",
42            Phase::RequestBody => "REQUEST_BODY",
43            Phase::ResponseHeaders => "RESPONSE_HEADERS",
44            Phase::ResponseBody => "RESPONSE_BODY",
45            Phase::Logging => "LOGGING",
46        }
47    }
48
49    /// Create from phase number.
50    pub fn from_number(n: u8) -> Option<Self> {
51        match n {
52            1 => Some(Phase::RequestHeaders),
53            2 => Some(Phase::RequestBody),
54            3 => Some(Phase::ResponseHeaders),
55            4 => Some(Phase::ResponseBody),
56            5 => Some(Phase::Logging),
57            _ => None,
58        }
59    }
60
61    /// Get all phases in order.
62    pub fn all() -> &'static [Phase] {
63        &[
64            Phase::RequestHeaders,
65            Phase::RequestBody,
66            Phase::ResponseHeaders,
67            Phase::ResponseBody,
68            Phase::Logging,
69        ]
70    }
71
72    /// Check if this is a request phase.
73    pub fn is_request_phase(&self) -> bool {
74        matches!(self, Phase::RequestHeaders | Phase::RequestBody)
75    }
76
77    /// Check if this is a response phase.
78    pub fn is_response_phase(&self) -> bool {
79        matches!(self, Phase::ResponseHeaders | Phase::ResponseBody)
80    }
81}
82
83impl Default for Phase {
84    fn default() -> Self {
85        Phase::RequestHeaders
86    }
87}
88
89impl TryFrom<u8> for Phase {
90    type Error = ();
91
92    fn try_from(value: u8) -> Result<Self, Self::Error> {
93        Phase::from_number(value).ok_or(())
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_phase_number() {
103        assert_eq!(Phase::RequestHeaders.number(), 1);
104        assert_eq!(Phase::RequestBody.number(), 2);
105        assert_eq!(Phase::ResponseHeaders.number(), 3);
106        assert_eq!(Phase::ResponseBody.number(), 4);
107        assert_eq!(Phase::Logging.number(), 5);
108    }
109
110    #[test]
111    fn test_phase_from_number() {
112        assert_eq!(Phase::from_number(1), Some(Phase::RequestHeaders));
113        assert_eq!(Phase::from_number(2), Some(Phase::RequestBody));
114        assert_eq!(Phase::from_number(6), None);
115    }
116
117    #[test]
118    fn test_is_request_phase() {
119        assert!(Phase::RequestHeaders.is_request_phase());
120        assert!(Phase::RequestBody.is_request_phase());
121        assert!(!Phase::ResponseHeaders.is_request_phase());
122    }
123}