1pub mod policy;
104
105use crate::{Method, Request, Response, StatusCode, StreamingBody, header::LOCATION};
106use iri_string::types::{UriAbsoluteString, UriReferenceStr};
107use rama_core::{
108 Layer, Service,
109 extensions::{Extension, Extensions, ExtensionsRef},
110};
111use rama_http_types::{
112 HeaderMap,
113 header::{CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING},
114};
115use rama_net::uri::Uri;
116use rama_utils::macros::{define_inner_service_accessors, generate_set_and_with};
117use std::fmt;
118
119use self::policy::{Action, Attempt, Policy, Standard};
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
136#[non_exhaustive]
137pub enum RedirectExtensionsBehaviour {
138 #[default]
143 Preserve,
144 Fork,
150 Drop,
152}
153
154impl RedirectExtensionsBehaviour {
155 fn redirect_extensions(self, source: &Extensions) -> Extensions {
157 match self {
158 Self::Preserve => source.clone(),
159 Self::Fork => source.fork(),
160 Self::Drop => Extensions::new(),
161 }
162 }
163}
164
165#[derive(Clone)]
169pub struct FollowRedirectLayer<P = Standard> {
170 policy: P,
171 extensions_behaviour: RedirectExtensionsBehaviour,
172}
173
174impl FollowRedirectLayer {
175 #[must_use]
177 pub fn new() -> Self {
178 Self::default()
179 }
180}
181
182impl Default for FollowRedirectLayer {
183 fn default() -> Self {
184 Self::with_policy(Standard::default())
185 }
186}
187
188impl<P: fmt::Debug> fmt::Debug for FollowRedirectLayer<P> {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 f.debug_struct("FollowRedirectLayer")
191 .field("policy", &self.policy)
192 .field("extensions_behaviour", &self.extensions_behaviour)
193 .finish()
194 }
195}
196
197impl<P> FollowRedirectLayer<P> {
198 pub fn with_policy(policy: P) -> Self {
200 Self {
201 policy,
202 extensions_behaviour: RedirectExtensionsBehaviour::default(),
203 }
204 }
205
206 generate_set_and_with! {
207 pub fn redirect_extensions_behaviour(
211 mut self,
212 behaviour: RedirectExtensionsBehaviour,
213 ) -> Self {
214 self.extensions_behaviour = behaviour;
215 self
216 }
217 }
218}
219
220impl<S, P> Layer<S> for FollowRedirectLayer<P>
221where
222 S: Clone,
223 P: Clone,
224{
225 type Service = FollowRedirect<S, P>;
226
227 fn layer(&self, inner: S) -> Self::Service {
228 FollowRedirect {
229 inner,
230 policy: self.policy.clone(),
231 extensions_behaviour: self.extensions_behaviour,
232 }
233 }
234
235 fn into_layer(self, inner: S) -> Self::Service {
236 FollowRedirect {
237 inner,
238 policy: self.policy,
239 extensions_behaviour: self.extensions_behaviour,
240 }
241 }
242}
243
244#[derive(Debug, Clone)]
248pub struct FollowRedirect<S, P = Standard> {
249 inner: S,
250 policy: P,
251 extensions_behaviour: RedirectExtensionsBehaviour,
252}
253
254impl<S> FollowRedirect<S> {
255 pub fn new(inner: S) -> Self {
257 Self::with_policy(inner, Standard::default())
258 }
259}
260
261impl<S, P> FollowRedirect<S, P> {
262 pub fn with_policy(inner: S, policy: P) -> Self {
264 Self {
265 inner,
266 policy,
267 extensions_behaviour: RedirectExtensionsBehaviour::default(),
268 }
269 }
270
271 generate_set_and_with! {
272 pub fn redirect_extensions_behaviour(
276 mut self,
277 behaviour: RedirectExtensionsBehaviour,
278 ) -> Self {
279 self.extensions_behaviour = behaviour;
280 self
281 }
282 }
283
284 define_inner_service_accessors!();
285}
286
287impl<ReqBody, ResBody, S, P> Service<Request<ReqBody>> for FollowRedirect<S, P>
288where
289 S: Service<Request<ReqBody>, Output = Response<ResBody>>,
290 ReqBody: StreamingBody + Default + Send + 'static,
291 ResBody: Send + 'static,
292 P: Policy<ReqBody, S::Error> + Clone,
293{
294 type Output = Response<ResBody>;
295 type Error = S::Error;
296
297 fn serve(
298 &self,
299
300 mut req: Request<ReqBody>,
301 ) -> impl Future<Output = Result<Self::Output, Self::Error>> {
302 let mut method = req.method().clone();
303 let mut uri = req.uri().clone();
304 let version = req.version();
305 let mut headers = req.headers().clone();
306
307 let mut policy = self.policy.clone();
308
309 let mut body = BodyRepr::None;
310 body.try_clone_from(&mut policy, req.body());
311 policy.on_request(&mut req);
312
313 let extensions_behaviour = self.extensions_behaviour;
316 let extensions_source = req.extensions().clone();
317
318 let service = &self.inner;
319
320 async move {
321 loop {
322 let res = service.serve(req).await?;
323 res.extensions().insert(RequestUri(uri.clone()));
324
325 let previous_method = method.clone();
326 let drop_payload_headers = |headers: &mut HeaderMap| {
327 for header in &[
328 CONTENT_TYPE,
329 CONTENT_LENGTH,
330 CONTENT_ENCODING,
331 TRANSFER_ENCODING,
332 ] {
333 headers.remove(header);
334 }
335 };
336
337 match res.status() {
338 StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND => {
339 if method == Method::POST {
342 method = Method::GET;
343 body = BodyRepr::Empty;
344 drop_payload_headers(&mut headers);
345 }
346 }
347 StatusCode::SEE_OTHER => {
348 if method != Method::HEAD {
350 method = Method::GET;
351 }
352 body = BodyRepr::Empty;
353 drop_payload_headers(&mut headers);
354 }
355 StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {}
356 _ => return Ok(res),
357 };
358
359 let Some(taken_body) = body.take() else {
360 return Ok(res);
361 };
362
363 let location = res
364 .headers()
365 .get(&LOCATION)
366 .and_then(|loc| resolve_uri(std::str::from_utf8(loc.as_bytes()).ok()?, &uri));
367 let Some(location) = location else {
368 return Ok(res);
369 };
370
371 let attempt = Attempt {
372 status: res.status(),
373 method: &method,
374 location: &location,
375 previous_method: &previous_method,
376 previous: &uri,
377 };
378 match policy.redirect(&attempt)? {
379 Action::Follow => {
380 uri = location;
381 body.try_clone_from(&mut policy, &taken_body);
382
383 req = Request::new(taken_body);
384 *req.uri_mut() = uri.clone();
385 *req.method_mut() = method.clone();
386 *req.version_mut() = version;
387 *req.headers_mut() = headers.clone();
388 req.set_extensions(
389 extensions_behaviour.redirect_extensions(&extensions_source),
390 );
391 policy.on_request(&mut req);
392 headers = req.headers().clone();
396 }
397 Action::Stop => return Ok(res),
398 }
399 }
400 }
401 }
402}
403
404#[derive(Debug, Clone, Extension)]
410#[extension(tags(http))]
411pub struct RequestUri(pub Uri);
412
413#[derive(Debug)]
414enum BodyRepr<B> {
415 Some(B),
416 Empty,
417 None,
418}
419
420impl<B> BodyRepr<B>
421where
422 B: StreamingBody + Default,
423{
424 fn take(&mut self) -> Option<B> {
425 match std::mem::replace(self, Self::None) {
426 Self::Some(body) => Some(body),
427 Self::Empty => {
428 *self = Self::Empty;
429 Some(B::default())
430 }
431 Self::None => None,
432 }
433 }
434
435 fn try_clone_from<P, E>(&mut self, policy: &mut P, body: &B)
436 where
437 P: Policy<B, E>,
438 {
439 match self {
440 Self::Some(_) | Self::Empty => {}
441 Self::None => {
442 if let Some(body) = clone_body(policy, body) {
443 *self = Self::Some(body);
444 }
445 }
446 }
447 }
448}
449
450fn clone_body<P, B, E>(policy: &mut P, body: &B) -> Option<B>
451where
452 P: Policy<B, E>,
453 B: StreamingBody + Default,
454{
455 if body.size_hint().exact() == Some(0) {
456 Some(B::default())
457 } else {
458 policy.clone_body(body)
459 }
460}
461
462fn resolve_uri(relative: &str, base: &Uri) -> Option<Uri> {
464 let relative = UriReferenceStr::new(relative).ok()?;
465 let base = UriAbsoluteString::try_from(base.to_string()).ok()?;
466 let uri = relative.resolve_against(&base).to_string();
467 Uri::try_from(uri).ok()
468}
469
470#[cfg(test)]
477mod tests {
478 use super::{policy::*, *};
479 use crate::{Body, header::LOCATION};
480 use rama_core::Layer;
481 use rama_core::extensions::ExtensionsRef;
482 use rama_core::service::service_fn;
483 use std::convert::Infallible;
484
485 #[tokio::test]
486 async fn follows() {
487 let svc = FollowRedirectLayer::with_policy(Action::Follow).into_layer(service_fn(handle));
488 let req = Request::builder()
489 .uri("http://example.com/42")
490 .body(Body::empty())
491 .unwrap();
492 let res = svc.serve(req).await.unwrap();
493 assert_eq!(*res.body(), 0);
494 assert_eq!(
495 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
496 "http://example.com/0"
497 );
498 }
499
500 #[tokio::test]
501 async fn stops() {
502 let svc = FollowRedirectLayer::with_policy(Action::Stop).into_layer(service_fn(handle));
503 let req = Request::builder()
504 .uri("http://example.com/42")
505 .body(Body::empty())
506 .unwrap();
507 let res = svc.serve(req).await.unwrap();
508 assert_eq!(*res.body(), 42);
509 assert_eq!(
510 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
511 "http://example.com/42"
512 );
513 }
514
515 #[tokio::test]
516 async fn limited() {
517 let svc = FollowRedirectLayer::with_policy(Limited::new(10)).into_layer(service_fn(handle));
518 let req = Request::builder()
519 .uri("http://example.com/42")
520 .body(Body::empty())
521 .unwrap();
522 let res = svc.serve(req).await.unwrap();
523 assert_eq!(*res.body(), 42 - 10);
524 assert_eq!(
525 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
526 "http://example.com/32"
527 );
528 }
529
530 async fn handle<B>(req: Request<B>) -> Result<Response<u64>, Infallible> {
533 let n: u64 = req
534 .uri()
535 .first_path_segment()
536 .and_then(|segment| segment.as_encoded_str().parse().ok())
537 .unwrap();
538 let mut res = Response::builder();
539 if n > 0 {
540 res = res
541 .status(StatusCode::MOVED_PERMANENTLY)
542 .header(LOCATION, format!("/{}", n - 1));
543 }
544 Ok::<_, Infallible>(res.body(n).unwrap())
545 }
546
547 #[derive(Clone, Debug, PartialEq, rama_core::extensions::Extension)]
548 struct Marker(u32);
549
550 async fn handle_marker<B>(req: Request<B>) -> Result<Response<u64>, Infallible> {
553 let n: u64 = req
554 .uri()
555 .first_path_segment()
556 .and_then(|segment| segment.as_encoded_str().parse().ok())
557 .unwrap();
558 let mut res = Response::builder();
559 if n > 0 {
560 res = res
561 .status(StatusCode::MOVED_PERMANENTLY)
562 .header(LOCATION, format!("/{}", n - 1));
563 }
564 let res = res.body(n).unwrap();
565 if let Some(marker) = req.extensions().get_ref::<Marker>() {
566 res.extensions().insert(marker.clone());
567 }
568 Ok::<_, Infallible>(res)
569 }
570
571 #[tokio::test]
572 async fn preserves_extensions_by_default() {
573 let svc = FollowRedirectLayer::new().into_layer(service_fn(handle_marker));
574 let req = Request::builder()
575 .uri("http://example.com/3")
576 .body(Body::empty())
577 .unwrap();
578 req.extensions().insert(Marker(7));
579 let res = svc.serve(req).await.unwrap();
580 assert_eq!(res.extensions().get_ref::<Marker>(), Some(&Marker(7)));
582 }
583
584 #[tokio::test]
585 async fn preserve_shares_extensions() {
586 let svc = FollowRedirectLayer::new()
587 .with_redirect_extensions_behaviour(RedirectExtensionsBehaviour::Preserve)
588 .into_layer(service_fn(handle_marker));
589 let req = Request::builder()
590 .uri("http://example.com/3")
591 .body(Body::empty())
592 .unwrap();
593 req.extensions().insert(Marker(7));
594 let res = svc.serve(req).await.unwrap();
595 assert_eq!(res.extensions().get_ref::<Marker>(), Some(&Marker(7)));
596 }
597
598 async fn handle_cookie_chain<B>(req: Request<B>) -> Result<Response<u64>, Infallible> {
602 let host = req.uri().host_str();
603 let path = req.uri().path_ref_or_root();
604 let location = if host.as_deref() == Some("a.example.com") {
605 Some("http://b.example.com/second")
606 } else if host.as_deref() == Some("b.example.com") && path == "/second" {
607 Some("http://b.example.com/final")
608 } else {
609 None
610 };
611 let mut res = Response::builder();
612 if let Some(location) = location {
613 res = res
614 .status(StatusCode::MOVED_PERMANENTLY)
615 .header(LOCATION, location);
616 }
617 let mut res = res.body(0u64).unwrap();
618 if req.headers().contains_key(crate::header::COOKIE) {
619 res.headers_mut()
620 .insert("x-saw-cookie", crate::HeaderValue::from_static("1"));
621 }
622 Ok::<_, Infallible>(res)
623 }
624
625 #[tokio::test]
626 async fn credentials_do_not_resurrect_after_cross_origin() {
627 let svc = FollowRedirectLayer::default().into_layer(service_fn(handle_cookie_chain));
631 let req = Request::builder()
632 .uri("http://a.example.com/")
633 .header(crate::header::COOKIE, "session=secret")
634 .body(Body::empty())
635 .unwrap();
636 let res = svc.serve(req).await.unwrap();
637 assert!(
638 !res.headers().contains_key("x-saw-cookie"),
639 "Cookie resurrected on a same-origin hop after being dropped cross-origin",
640 );
641 assert_eq!(
642 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
643 "http://b.example.com/final"
644 );
645 }
646
647 #[tokio::test]
648 async fn drop_extensions_opt_out() {
649 let svc = FollowRedirectLayer::new()
650 .with_redirect_extensions_behaviour(RedirectExtensionsBehaviour::Drop)
651 .into_layer(service_fn(handle_marker));
652 let req = Request::builder()
653 .uri("http://example.com/3")
654 .body(Body::empty())
655 .unwrap();
656 req.extensions().insert(Marker(7));
657 let res = svc.serve(req).await.unwrap();
658 assert!(res.extensions().get_ref::<Marker>().is_none());
660 }
661
662 #[tokio::test]
663 async fn test_301_redirects() {
664 let policy = policy::redirect_fn(|attempt| -> Result<_, Infallible> {
665 if attempt.previous_method() == Method::POST && attempt.method() == Method::GET {
666 Ok(Action::Stop)
667 } else {
668 Ok(Action::Follow)
669 }
670 });
671 let svc = FollowRedirectLayer::with_policy(policy).into_layer(service_fn(redirections));
672
673 {
676 let req = Request::builder()
677 .method(Method::POST)
678 .uri("http://example.com/301")
679 .body(Body::empty())
680 .unwrap();
681 let res = svc.clone().serve(req).await.unwrap();
682 assert_eq!(*res.body(), "/target/301");
683 assert_eq!(
684 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
685 "http://example.com/301"
686 );
687 }
688
689 {
692 let req = Request::builder()
693 .method(Method::GET)
694 .uri("http://example.com/301")
695 .body(Body::empty())
696 .unwrap();
697 let res = svc.clone().serve(req).await.unwrap();
698 assert_eq!(*res.body(), "/target/301/final");
699 assert_eq!(
700 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
701 "http://example.com/target/301"
702 );
703 }
704 }
705
706 #[tokio::test]
707 async fn test_302_redirects() {
708 let policy = policy::redirect_fn(|attempt| -> Result<_, Infallible> {
709 if attempt.previous_method() != attempt.method() {
710 Ok(Action::Stop)
711 } else {
712 Ok(Action::Follow)
713 }
714 });
715 let svc = FollowRedirectLayer::with_policy(policy).into_layer(service_fn(redirections));
716
717 {
720 let req = Request::builder()
721 .method(Method::POST)
722 .uri("http://example.com/302")
723 .body(Body::empty())
724 .unwrap();
725 let res = svc.clone().serve(req).await.unwrap();
726 assert_eq!(*res.body(), "/target/302");
727 assert_eq!(
728 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
729 "http://example.com/302"
730 );
731 }
732
733 {
736 let req = Request::builder()
737 .method(Method::PUT)
738 .uri("http://example.com/302")
739 .body(Body::empty())
740 .unwrap();
741 let res = svc.clone().serve(req).await.unwrap();
742 assert_eq!(*res.body(), "/target/302/final");
743 assert_eq!(
744 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
745 "http://example.com/target/302"
746 );
747 }
748
749 {
752 let req = Request::builder()
753 .method(Method::HEAD)
754 .uri("http://example.com/302")
755 .body(Body::empty())
756 .unwrap();
757 let res = svc.clone().serve(req).await.unwrap();
758 assert_eq!(*res.body(), "/target/302/final");
759 assert_eq!(
760 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
761 "http://example.com/target/302"
762 );
763 }
764 }
765
766 #[tokio::test]
767 async fn test_303_redirects() {
768 let policy = policy::redirect_fn(|attempt| -> Result<_, Infallible> {
769 if attempt.previous_method() != attempt.method() {
770 Ok(Action::Stop)
771 } else {
772 Ok(Action::Follow)
773 }
774 });
775 let svc = FollowRedirectLayer::with_policy(policy).into_layer(service_fn(redirections));
776
777 {
780 let req = Request::builder()
781 .method(Method::POST)
782 .uri("http://example.com/303")
783 .body(Body::empty())
784 .unwrap();
785 let res = svc.clone().serve(req).await.unwrap();
786 assert_eq!(*res.body(), "/target/303");
787 assert_eq!(
788 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
789 "http://example.com/303"
790 );
791 }
792
793 {
796 let req = Request::builder()
797 .method(Method::PUT)
798 .uri("http://example.com/303")
799 .body(Body::empty())
800 .unwrap();
801 let res = svc.clone().serve(req).await.unwrap();
802 assert_eq!(*res.body(), "/target/303");
803 assert_eq!(
804 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
805 "http://example.com/303"
806 );
807 }
808
809 {
812 let req = Request::builder()
813 .method(Method::HEAD)
814 .uri("http://example.com/303")
815 .body(Body::empty())
816 .unwrap();
817 let res = svc.clone().serve(req).await.unwrap();
818 assert_eq!(*res.body(), "/target/303/final");
819 assert_eq!(
820 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
821 "http://example.com/target/303"
822 );
823 }
824 }
825
826 #[tokio::test]
827 async fn test_307_308_redirects() {
828 let policy = policy::redirect_fn(|attempt| -> Result<_, Infallible> {
829 if attempt.previous_method() != Method::POST || attempt.method() != Method::POST {
830 Ok(Action::Stop)
831 } else {
832 Ok(Action::Follow)
833 }
834 });
835 let svc = FollowRedirectLayer::with_policy(policy).into_layer(service_fn(redirections));
836
837 {
840 let req = Request::builder()
841 .method(Method::POST)
842 .uri("http://example.com/307")
843 .body(Body::empty())
844 .unwrap();
845 let res = svc.clone().serve(req).await.unwrap();
846 assert_eq!(*res.body(), "/target/307/final");
847 assert_eq!(
848 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
849 "http://example.com/target/307"
850 );
851 }
852
853 {
856 let req = Request::builder()
857 .method(Method::POST)
858 .uri("http://example.com/308")
859 .body(Body::empty())
860 .unwrap();
861 let res = svc.clone().serve(req).await.unwrap();
862 assert_eq!(*res.body(), "/target/308/final");
863 assert_eq!(
864 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
865 "http://example.com/target/308"
866 );
867 }
868 }
869
870 async fn redirections<B>(req: Request<B>) -> Result<Response<String>, Infallible> {
872 let path = req.uri().path_ref_or_root();
873 let mut res = Response::builder();
874 let body_str;
875 res = if path == "/301" {
876 let case = "/target/301";
877 body_str = case.to_owned();
878 res.status(StatusCode::MOVED_PERMANENTLY)
879 .header(LOCATION, case)
880 } else if path == "/302" {
881 let case = "/target/302";
882 body_str = case.to_owned();
883 res.status(StatusCode::FOUND).header(LOCATION, case)
884 } else if path == "/303" {
885 let case = "/target/303";
886 body_str = case.to_owned();
887 res.status(StatusCode::SEE_OTHER).header(LOCATION, case)
888 } else if path == "/307" {
889 let case = "/target/307";
890 body_str = case.to_owned();
891 res.status(StatusCode::TEMPORARY_REDIRECT)
892 .header(LOCATION, case)
893 } else if path == "/308" {
894 let case = "/target/308";
895 body_str = case.to_owned();
896 res.status(StatusCode::PERMANENT_REDIRECT)
897 .header(LOCATION, case)
898 } else {
899 body_str = format!("{path}/final");
900 res.status(StatusCode::OK)
901 };
902 Ok::<_, Infallible>(res.body(body_str).unwrap())
903 }
904
905 }