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".to_owned();
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".to_owned();
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<(), ExpressionError> {
80 let groups = ExpressionList::from_str(rules)?.groups();
81 self.groups.extend(groups);
82 Ok(())
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: String) -> 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(
109 &self,
110 mut uri: String,
111 ctx: &mut EngineCtx,
112 ) -> Result<Rewrite, EngineError> {
113 for group in self.groups.iter().filter(|g| g.match_conditions(ctx)) {
114 uri = match group.rewrite(uri)? {
115 Rewrite::Uri(uri) => uri,
116 status => return Ok(status),
117 };
118 }
119 Ok(Rewrite::Uri(uri))
120 }
121}
122
123impl FromStr for Engine {
124 type Err = ExpressionError;
125
126 fn from_str(s: &str) -> Result<Self, Self::Err> {
127 let groups = ExpressionList::from_str(s)?.groups();
128 Ok(Self { groups })
129 }
130}