mod_rewrite/conditions/
mod.rs1use std::str::FromStr;
2
3pub mod context;
4mod error;
5mod matcher;
6mod parse;
7
8use matcher::{Match, Value};
9
10pub use context::EngineCtx;
11pub use error::CondError;
12
13#[derive(Clone, Debug)]
21pub struct Condition {
22 matcher: Match,
23 flags: Vec<CondFlag>,
24}
25
26impl Condition {
27 pub fn is_met(&self, ctx: &mut EngineCtx) -> bool {
29 let nocase = self.flags.iter().any(|f| matches!(f, CondFlag::NoCase));
30 match &self.matcher {
31 Match::Pattern(v1, pt, v2) => {
32 pt.matches(Value::new(v1, nocase, ctx), Value::new(v2, nocase, ctx))
33 }
34 Match::NotPattern(v1, pt, v2) => {
35 !pt.matches(Value::new(v1, nocase, ctx), Value::new(v2, nocase, ctx))
36 }
37 Match::Compare(v1, cp, v2) => {
38 cp.compare(Value::new(v1, nocase, ctx), Value::new(v2, nocase, ctx))
39 }
40 Match::FileTest(v1, ft) => ft.matches(Value::new(v1, nocase, ctx)),
41 Match::NotFileTest(v1, ft) => !ft.matches(Value::new(v1, nocase, ctx)),
42 }
43 }
44
45 #[inline]
48 pub fn is_or(&self) -> bool {
49 self.flags.iter().any(|c| matches!(c, CondFlag::Or))
50 }
51}
52
53impl FromStr for Condition {
54 type Err = CondError;
55
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 let mut tokens = parse::tokenize(s)?.into_iter().peekable();
58 let matcher = Match::parse(&mut tokens)?;
59 let flags = match tokens.next() {
60 Some(flags) => CondFlagList::from_str(&flags)?.0,
61 None => Vec::new(),
62 };
63 Ok(Self { matcher, flags })
64 }
65}
66
67struct CondFlagList(Vec<CondFlag>);
68
69impl FromStr for CondFlagList {
70 type Err = CondError;
71
72 fn from_str(s: &str) -> Result<Self, Self::Err> {
73 if !s.starts_with('[') || !s.ends_with(']') {
74 return Err(CondError::FlagsMissingBrackets(s.to_owned()));
75 }
76 let flags = s[1..s.len() - 1]
77 .split(',')
78 .map(|s| s.trim())
79 .filter(|s| !s.is_empty())
80 .map(CondFlag::from_str)
81 .collect::<Result<Vec<CondFlag>, _>>()?;
82 if flags.is_empty() {
83 return Err(CondError::FlagsEmpty);
84 }
85 Ok(Self(flags))
86 }
87}
88
89#[derive(Clone, Debug)]
92enum CondFlag {
93 NoCase,
94 Or,
95}
96
97impl FromStr for CondFlag {
98 type Err = CondError;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 match s.to_lowercase().as_str() {
102 "i" | "insensitive" | "nc" | "nocase" => Ok(Self::NoCase),
103 "or" | "ornext" => Ok(Self::Or),
104 _ => Err(CondError::InvalidFlag(s.to_owned())),
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 use context::{RequestCtx, ServerCtx};
114 use matcher::{Compare, FileTest, Pattern};
115
116 #[test]
117 fn test_pattern() {
118 let s1 = String::from("%{REQUEST_URI}");
119 let s2 = String::from("/Test");
120 let cond = Condition::from_str(&format!(r#"{s1} "={s2}" [NC,OR]"#)).unwrap();
121 assert!(matches!(
122 &cond.matcher,
123 Match::Pattern(v1, Pattern::Equals, v2) if v1 == &s1 && v2 == &s2,
124 ));
125 assert_eq!(cond.flags.len(), 2);
126 assert!(matches!(cond.flags.get(0), Some(CondFlag::NoCase)));
127
128 let mut req = RequestCtx::default().request_uri("/Test");
129 let mut ctx = EngineCtx::default().with_ctx(req);
130 assert!(cond.is_met(&mut ctx));
131
132 req = RequestCtx::default().request_uri("/Not");
133 let mut ctx = EngineCtx::default().with_ctx(req);
134 assert!(!cond.is_met(&mut ctx));
135 }
136
137 #[test]
138 fn test_compare() {
139 let s1 = String::from("%{SERVER_PORT}");
140 let s2 = String::from("4000");
141 let cond = Condition::from_str(&format!("{s1} -ge {s2}")).unwrap();
142 assert!(matches!(
143 &cond.matcher,
144 Match::Compare(v1, Compare::GreaterOrEqual, v2) if v1 == &s1 && v2 == &s2,
145 ));
146 assert_eq!(cond.flags.len(), 0);
147
148 let mut srv = ServerCtx::default().server_addr("127.0.0.1:4001").unwrap();
149 let mut ctx = EngineCtx::default().with_ctx(srv);
150 assert!(cond.is_met(&mut ctx));
151
152 srv = ServerCtx::default().server_addr("127.0.0.1:3999").unwrap();
153 let mut ctx = EngineCtx::default().with_ctx(srv);
154 assert!(!cond.is_met(&mut ctx));
155 }
156
157 #[test]
158 fn test_filetest() {
159 let s1 = String::from("%{REQUEST_URI}");
160 let cond = Condition::from_str(&format!("{s1} !-f")).unwrap();
161 assert!(matches!(
162 &cond.matcher,
163 Match::NotFileTest(v1, FileTest::File) if v1 == &s1,
164 ));
165 assert_eq!(cond.flags.len(), 0);
166
167 let current = std::env::current_dir().unwrap().join("src").join("lib.rs");
168 let mut req = RequestCtx::default().request_uri(current.to_str().unwrap());
169 let mut ctx = EngineCtx::default().with_ctx(req);
170 assert!(!cond.is_met(&mut ctx));
171
172 req = RequestCtx::default().request_uri("/invalid");
173 let mut ctx = EngineCtx::default().with_ctx(req);
174 assert!(cond.is_met(&mut ctx));
175 }
176}