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}