@@ -539,14 +539,17 @@ fn redirect(url: Url) -> Response {
539
539
resp
540
540
}
541
541
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 ( (
544
547
StatusCode :: FOUND ,
545
548
[ (
546
549
http:: header:: LOCATION ,
547
550
http:: HeaderValue :: try_from ( url) . expect ( "invalid url for redirect" ) ,
548
551
) ] ,
549
- )
552
+ ) )
550
553
}
551
554
552
555
fn cached_redirect ( url : Url , cache_policy : cache:: CachePolicy ) -> Response {
@@ -555,10 +558,13 @@ fn cached_redirect(url: Url, cache_policy: cache::CachePolicy) -> Response {
555
558
resp
556
559
}
557
560
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 ( ) ;
560
566
resp. extensions_mut ( ) . insert ( cache_policy) ;
561
- resp
567
+ Ok ( resp)
562
568
}
563
569
564
570
fn redirect_base ( req : & Request ) -> String {
@@ -707,8 +713,10 @@ impl_axum_webpage! {
707
713
mod test {
708
714
use super :: * ;
709
715
use crate :: { docbuilder:: DocCoverage , test:: * , web:: match_version} ;
716
+ use axum:: http:: StatusCode ;
710
717
use kuchiki:: traits:: TendrilSink ;
711
718
use serde_json:: json;
719
+ use test_case:: test_case;
712
720
713
721
fn release ( version : & str , env : & TestEnvironment ) -> i32 {
714
722
env. fake_release ( )
@@ -1158,4 +1166,42 @@ mod test {
1158
1166
Ok ( ( ) )
1159
1167
} ) ;
1160
1168
}
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
+ }
1161
1207
}
0 commit comments