mod_rewrite/lib.rs
1//! Framework agnostic reimplementation of HTTPD's [mod_rewrite](https://httpd.apache.org/docs/current/mod/mod_rewrite.html).
2//!
3//! # Example
4//!
5//! ```
6//! use mod_rewrite::Engine;
7//!
8//! let mut engine = Engine::default();
9//! engine.add_rules(r#"
10//! RewriteRule /file/(.*) /tmp/$1 [L]
11//! RewriteRule /redirect/(.*) /location/$1 [R=302]
12//! RewriteRule /blocked/(.*) - [F]
13//! "#).expect("failed to process rules");
14//!
15//! let uri = "http://localhost/file/my/document.txt";
16//! let result = engine.rewrite(uri).unwrap();
17//! println!("{result:?}");
18//! ```
19use std::str::FromStr;
20
21mod conditions;
22pub mod error;
23mod expr;
24mod extra;
25mod rule;
26
27use conditions::EngineCtx;
28use error::{EngineError, ExpressionError};
29use expr::ExpressionList;
30
31pub use conditions::{Condition, context};
32pub use expr::{ExprGroup, Expression, Rewrite};
33pub use extra::State;
34pub use rule::Rule;
35
36/// Expression Engine for Proccessing Rewrite Rules
37///
38/// Supports a subset of [official](https://httpd.apache.org/docs/current/mod/mod_rewrite.html)
39/// `mod_rewrite` expressions.
40///
41/// # Example
42///
43/// ```
44/// use mod_rewrite::Engine;
45///
46/// let mut engine = Engine::default();
47/// engine.add_rules(r#"
48/// RewriteRule /file/(.*) /tmp/$1 [L]
49/// RewriteRule /redirect/(.*) /location/$1 [R=302]
50/// RewriteRule /blocked/(.*) - [F]
51/// "#).expect("failed to process rules");
52///
53/// let uri = "http://localhost/file/my/document.txt";
54/// let result = engine.rewrite(uri).unwrap();
55/// println!("{result:?}");
56/// ```
57#[derive(Debug, Default)]
58pub struct Engine {
59 groups: Vec<ExprGroup>,
60}
61
62impl Engine {
63 /// Configure max number of loops over entire ruleset during
64 /// rewrite before error
65 ///
66 /// Default is 10
67 pub fn max_iterations(mut self, iterations: usize) -> Self {
68 self.groups = self
69 .groups
70 .into_iter()
71 .map(|g| g.max_iterations(iterations))
72 .collect();
73 self
74 }
75
76 /// Parse additonal [`Expression`]s to append as [`ExprGroup`]s to the
77 /// existing engine.
78 #[inline]
79 pub fn add_rules(&mut self, rules: &str) -> Result<&mut Self, ExpressionError> {
80 let groups = ExpressionList::from_str(rules)?.groups();
81 self.groups.extend(groups);
82 Ok(self)
83 }
84
85 /// Evaluate the given URI against the configured [`ExprGroup`] instances
86 /// defined and generate a [`Rewrite`] response.
87 ///
88 /// This method skips using [`EngineCtx`] which is used to suppliment
89 /// [`Condition`] expressions. If you are NOT making use of `RewriteCond`
90 /// rules, this method may be simpler to use.
91 ///
92 /// See [`Engine::rewrite_ctx`] for more details.
93 #[inline]
94 pub fn rewrite(&self, uri: &str) -> Result<Rewrite, EngineError> {
95 let mut ctx = EngineCtx::default();
96 self.rewrite_ctx(uri, &mut ctx)
97 }
98
99 /// Evaluate the given URI against the configured [`ExprGroup`] instances
100 /// defined and generate a [`Rewrite`] response.
101 ///
102 /// This method uses an additional [`EngineCtx`] which is used to suppliment
103 /// variables expanded in [`Condition`] expressions.
104 ///
105 /// If your engine is using `RewriteCond` rules, you will want to use this
106 /// method with a complete `EngineCtx`. See [`Engine::rewrite`] for a simpler
107 /// alternative.
108 pub fn rewrite_ctx(&self, uri: &str, ctx: &mut EngineCtx) -> Result<Rewrite, EngineError> {
109 let (mut uri, query) = extra::split_query(uri);
110 for group in self.groups.iter().filter(|g| g.match_conditions(ctx)) {
111 uri = match group.rewrite(&uri)? {
112 Rewrite::Uri(uri) => uri,
113 status => return Ok(status.with_query(query)),
114 };
115 }
116 Ok(Rewrite::Uri(uri).with_query(query))
117 }
118}
119
120impl FromStr for Engine {
121 type Err = ExpressionError;
122
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 let groups = ExpressionList::from_str(s)?.groups();
125 Ok(Self { groups })
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_groups() {
135 let mut engine = Engine::default();
136 engine
137 .add_rules(
138 r#"
139 RewriteRule /static/(.*) /files/$1 [NE,L]
140
141 RewriteRule /(.*) /index?page=$1
142 "#,
143 )
144 .unwrap();
145
146 let r = engine.rewrite("/static/1/2").unwrap();
147 assert!(matches!(r, Rewrite::Uri(uri) if uri == "/index?page=files%2F1%2F2"));
148
149 let r = engine.rewrite("/1/2/3?a=b").unwrap();
150 println!("{r:?}");
151 assert!(matches!(r, Rewrite::Uri(uri) if uri == "/index?page=1%2F2%2F3&a=b"));
152 }
153
154 #[test]
155 fn test_query() {
156 let mut engine = Engine::default();
157 engine
158 .add_rules(
159 r#"
160 RewriteRule /static/(.*) /files/$1 [NE,END]
161
162 RewriteRule /(.*) /index?page=$1
163 "#,
164 )
165 .unwrap();
166
167 let r = engine.rewrite("/static/1/2?a=b").unwrap();
168 assert!(matches!(r, Rewrite::EndUri(uri) if uri == "/files/1/2?a=b"));
169
170 let r = engine.rewrite("/1/2/3?a=b").unwrap();
171 println!("{r:?}");
172 assert!(matches!(r, Rewrite::Uri(uri) if uri == "/index?page=1%2F2%2F3&a=b"));
173 }
174}