1use headers::HeaderValue;
10use hyper::{header::HOST, Body, Request, Response, StatusCode, Uri};
11
12use crate::{
13 handler::RequestHandlerOpts,
14 redirects::{handle_error, replace_placeholders},
15 settings::{file::RedirectsKind, Rewrites},
16 Error,
17};
18
19pub(crate) fn pre_process<T>(
21 opts: &RequestHandlerOpts,
22 req: &mut Request<T>,
23) -> Option<Result<Response<Body>, Error>> {
24 let rewrites = opts.advanced_opts.as_ref()?.rewrites.as_deref()?;
25 let uri_path = req.uri().path();
26
27 let matched = rewrite_uri_path(uri_path, Some(rewrites))?;
28 let dest = match replace_placeholders(uri_path, &matched.source, &matched.destination) {
29 Ok(dest) => dest,
30 Err(err) => return handle_error(err, opts, req),
31 };
32
33 if let Some(redirect_type) = &matched.redirect {
34 let loc = match HeaderValue::from_str(&dest) {
36 Ok(val) => val,
37 Err(err) => {
38 return handle_error(
39 Error::new(err).context("invalid header value from current uri"),
40 opts,
41 req,
42 )
43 }
44 };
45 let mut resp = Response::new(Body::empty());
46 resp.headers_mut().insert(hyper::header::LOCATION, loc);
47 *resp.status_mut() = match redirect_type {
48 RedirectsKind::Permanent => StatusCode::MOVED_PERMANENTLY,
49 RedirectsKind::Temporary => StatusCode::FOUND,
50 };
51 Some(Ok(resp))
52 } else {
53 *req.uri_mut() = match merge_uris(req.uri(), &dest) {
55 Ok(uri) => uri,
56 Err(err) => {
57 return handle_error(
58 err.context("invalid rewrite target from current uri"),
59 opts,
60 req,
61 )
62 }
63 };
64
65 if let Some(host) = req.uri().host() {
67 let mut host = host.to_owned();
68 if let Some(port) = req.uri().port_u16() {
69 host.push_str(&format!(":{}", port));
70 }
71 if let Ok(host) = host.parse() {
72 req.headers_mut().insert(HOST, host);
73 }
74 }
75
76 None
77 }
78}
79
80fn merge_uris(orig_uri: &Uri, new_uri: &str) -> Result<Uri, Error> {
81 let mut parts = new_uri.parse::<Uri>()?.into_parts();
82 if parts.scheme.is_none() {
83 parts.scheme = orig_uri.scheme().cloned();
84 }
85 if parts.authority.is_none() {
86 parts.authority = orig_uri.authority().cloned();
87 }
88 if parts.path_and_query.is_none() {
89 parts.path_and_query = orig_uri.path_and_query().cloned();
90 }
91 if let Some(path_and_query) = &mut parts.path_and_query {
92 if let (None, Some(query)) = (path_and_query.query(), orig_uri.query()) {
93 *path_and_query = [path_and_query.as_str(), "?", query]
94 .into_iter()
95 .collect::<String>()
96 .parse()?;
97 }
98 }
99 Ok(Uri::from_parts(parts)?)
100}
101
102pub fn rewrite_uri_path<'a>(
105 uri_path: &'a str,
106 rewrites_opts: Option<&'a [Rewrites]>,
107) -> Option<&'a Rewrites> {
108 if let Some(rewrites_vec) = rewrites_opts {
109 for rewrites_entry in rewrites_vec {
110 if rewrites_entry.source.is_match(uri_path) {
112 return Some(rewrites_entry);
113 }
114 }
115 }
116
117 None
118}
119
120#[cfg(test)]
121mod tests {
122 use super::pre_process;
123 use crate::{
124 handler::RequestHandlerOpts,
125 settings::{file::RedirectsKind, Advanced, Rewrites},
126 Error,
127 };
128 use hyper::{header::HOST, Body, Request, Response, StatusCode};
129 use regex::Regex;
130
131 fn make_request(host: &str, uri: &str) -> Request<Body> {
132 let mut builder = Request::builder();
133 if !host.is_empty() {
134 builder = builder.header("Host", host);
135 }
136 builder.method("GET").uri(uri).body(Body::empty()).unwrap()
137 }
138
139 fn get_rewrites() -> Vec<Rewrites> {
140 vec![
141 Rewrites {
142 source: Regex::new(r"/source1$").unwrap(),
143 destination: "/destination1".into(),
144 redirect: None,
145 },
146 Rewrites {
147 source: Regex::new(r"/source2$").unwrap(),
148 destination: "/destination2".into(),
149 redirect: Some(RedirectsKind::Temporary),
150 },
151 Rewrites {
152 source: Regex::new(r"/(prefix/)?(source3)/(.*)").unwrap(),
153 destination: "/destination3/$2/$3".into(),
154 redirect: Some(RedirectsKind::Permanent),
155 },
156 Rewrites {
157 source: Regex::new(r"/(source4)/(.*)").unwrap(),
158 destination: "http://example.net:1234/destination4/$1?$2".into(),
159 redirect: None,
160 },
161 ]
162 }
163
164 fn is_redirect(result: Option<Result<Response<Body>, Error>>) -> Option<(StatusCode, String)> {
165 if let Some(Ok(response)) = result {
166 let location = response.headers().get("Location")?.to_str().unwrap().into();
167 Some((response.status(), location))
168 } else {
169 None
170 }
171 }
172
173 #[test]
174 fn test_no_rewrites() {
175 let mut req = make_request("", "/");
176 assert!(pre_process(
177 &RequestHandlerOpts {
178 advanced_opts: None,
179 ..Default::default()
180 },
181 &mut req
182 )
183 .is_none());
184 assert_eq!(req.uri(), "/");
185
186 let mut req = make_request("", "/");
187 assert!(pre_process(
188 &RequestHandlerOpts {
189 advanced_opts: Some(Advanced {
190 rewrites: None,
191 ..Default::default()
192 }),
193 ..Default::default()
194 },
195 &mut req
196 )
197 .is_none());
198 assert_eq!(req.uri(), "/");
199 }
200
201 #[test]
202 fn test_no_match() {
203 let mut req = make_request("example.com", "/source2/whatever");
204 assert!(pre_process(
205 &RequestHandlerOpts {
206 advanced_opts: Some(Advanced {
207 rewrites: Some(get_rewrites()),
208 ..Default::default()
209 }),
210 ..Default::default()
211 },
212 &mut req
213 )
214 .is_none());
215 assert_eq!(req.uri(), "/source2/whatever");
216 }
217
218 #[test]
219 fn test_match() {
220 let mut req = make_request("", "/source1?query");
221 assert!(pre_process(
222 &RequestHandlerOpts {
223 advanced_opts: Some(Advanced {
224 rewrites: Some(get_rewrites()),
225 ..Default::default()
226 }),
227 ..Default::default()
228 },
229 &mut req
230 )
231 .is_none());
232 assert_eq!(req.uri(), "/destination1?query");
233
234 let mut req = make_request("", "/source2");
235 assert_eq!(
236 is_redirect(pre_process(
237 &RequestHandlerOpts {
238 advanced_opts: Some(Advanced {
239 rewrites: Some(get_rewrites()),
240 ..Default::default()
241 }),
242 ..Default::default()
243 },
244 &mut req
245 )),
246 Some((StatusCode::FOUND, "/destination2".into()))
247 );
248
249 let mut req = make_request("", "/source3/whatever");
250 assert_eq!(
251 is_redirect(pre_process(
252 &RequestHandlerOpts {
253 advanced_opts: Some(Advanced {
254 rewrites: Some(get_rewrites()),
255 ..Default::default()
256 }),
257 ..Default::default()
258 },
259 &mut req
260 )),
261 Some((
262 StatusCode::MOVED_PERMANENTLY,
263 "/destination3/source3/whatever".into()
264 ))
265 );
266
267 let mut req = make_request("example.com", "/source4/whatever?query");
268 assert!(pre_process(
269 &RequestHandlerOpts {
270 advanced_opts: Some(Advanced {
271 rewrites: Some(get_rewrites()),
272 ..Default::default()
273 }),
274 ..Default::default()
275 },
276 &mut req
277 )
278 .is_none());
279 assert_eq!(
280 req.uri(),
281 "http://example.net:1234/destination4/source4?whatever"
282 );
283 assert_eq!(
284 req.headers()
285 .get(HOST)
286 .map(|h| h.to_str().unwrap())
287 .unwrap_or(""),
288 "example.net:1234"
289 );
290 }
291}