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