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