@@ -539,14 +539,17 @@ fn redirect(url: Url) -> Response {
539539 resp
540540}
541541
542- fn axum_redirect ( url : & str ) -> impl IntoResponse {
543- (
542+ fn axum_redirect ( url : & str ) -> Result < impl IntoResponse , Error > {
543+ if !url. starts_with ( '/' ) || url. starts_with ( "//" ) {
544+ return Err ( anyhow ! ( "invalid redirect URL: {}" , url) ) ;
545+ }
546+ Ok ( (
544547 StatusCode :: FOUND ,
545548 [ (
546549 http:: header:: LOCATION ,
547550 http:: HeaderValue :: try_from ( url) . expect ( "invalid url for redirect" ) ,
548551 ) ] ,
549- )
552+ ) )
550553}
551554
552555fn cached_redirect ( url : Url , cache_policy : cache:: CachePolicy ) -> Response {
@@ -555,10 +558,13 @@ fn cached_redirect(url: Url, cache_policy: cache::CachePolicy) -> Response {
555558 resp
556559}
557560
558- fn axum_cached_redirect ( url : & str , cache_policy : cache:: CachePolicy ) -> impl IntoResponse {
559- let mut resp = axum_redirect ( url) . into_response ( ) ;
561+ fn axum_cached_redirect (
562+ url : & str ,
563+ cache_policy : cache:: CachePolicy ,
564+ ) -> Result < impl IntoResponse , Error > {
565+ let mut resp = axum_redirect ( url) ?. into_response ( ) ;
560566 resp. extensions_mut ( ) . insert ( cache_policy) ;
561- resp
567+ Ok ( resp)
562568}
563569
564570fn redirect_base ( req : & Request ) -> String {
@@ -707,8 +713,10 @@ impl_axum_webpage! {
707713mod test {
708714 use super :: * ;
709715 use crate :: { docbuilder:: DocCoverage , test:: * , web:: match_version} ;
716+ use axum:: http:: StatusCode ;
710717 use kuchiki:: traits:: TendrilSink ;
711718 use serde_json:: json;
719+ use test_case:: test_case;
712720
713721 fn release ( version : & str , env : & TestEnvironment ) -> i32 {
714722 env. fake_release ( )
@@ -1158,4 +1166,42 @@ mod test {
11581166 Ok ( ( ) )
11591167 } ) ;
11601168 }
1169+
1170+ #[ test]
1171+ fn test_axum_redirect ( ) {
1172+ let response = axum_redirect ( "/something" ) . unwrap ( ) . into_response ( ) ;
1173+ assert_eq ! ( response. status( ) , StatusCode :: FOUND ) ;
1174+ assert_eq ! (
1175+ response. headers( ) . get( http:: header:: LOCATION ) . unwrap( ) ,
1176+ "/something"
1177+ ) ;
1178+ assert ! ( response
1179+ . headers( )
1180+ . get( http:: header:: CACHE_CONTROL )
1181+ . is_none( ) ) ;
1182+ assert ! ( response. extensions( ) . get:: <cache:: CachePolicy >( ) . is_none( ) ) ;
1183+ }
1184+
1185+ #[ test]
1186+ fn test_axum_redirect_cached ( ) {
1187+ let response = axum_cached_redirect ( "/something" , cache:: CachePolicy :: NoCaching )
1188+ . unwrap ( )
1189+ . into_response ( ) ;
1190+ assert_eq ! ( response. status( ) , StatusCode :: FOUND ) ;
1191+ assert_eq ! (
1192+ response. headers( ) . get( http:: header:: LOCATION ) . unwrap( ) ,
1193+ "/something"
1194+ ) ;
1195+ assert ! ( matches!(
1196+ response. extensions( ) . get:: <cache:: CachePolicy >( ) . unwrap( ) ,
1197+ cache:: CachePolicy :: NoCaching ,
1198+ ) )
1199+ }
1200+
1201+ #[ test_case( "without_leading_slash" ) ]
1202+ #[ test_case( "//with_double_leading_slash" ) ]
1203+ fn test_axum_redirect_failure ( path : & str ) {
1204+ assert ! ( axum_redirect( path) . is_err( ) ) ;
1205+ assert ! ( axum_cached_redirect( path, cache:: CachePolicy :: NoCaching ) . is_err( ) ) ;
1206+ }
11611207}
0 commit comments