@@ -976,8 +976,11 @@ pub(crate) async fn json_download_handler(
976976 Some ( wanted_compression) ,
977977 ) ;
978978
979- let mut response = match storage. get_raw_stream ( & storage_path) . await {
980- Ok ( file) => StreamingFile ( file) . into_response ( if_none_match. as_deref ( ) ) ,
979+ let ( mut response, updated_storage_path) = match storage. get_raw_stream ( & storage_path) . await {
980+ Ok ( file) => (
981+ StreamingFile ( file) . into_response ( if_none_match. as_deref ( ) ) ,
982+ None ,
983+ ) ,
981984 Err ( err) if matches ! ( err. downcast_ref( ) , Some ( crate :: storage:: PathNotFoundError ) ) => {
982985 // we have old files on the bucket where we stored zstd compressed files,
983986 // with content-encoding=zstd & just a `.json` file extension.
@@ -992,8 +995,11 @@ pub(crate) async fn json_download_handler(
992995 ) ;
993996 // we have an old file with a `.json` extension,
994997 // redirect to that as fallback
995- StreamingFile ( storage. get_raw_stream ( & storage_path) . await ?)
996- . into_response ( if_none_match. as_deref ( ) )
998+ (
999+ StreamingFile ( storage. get_raw_stream ( & storage_path) . await ?)
1000+ . into_response ( if_none_match. as_deref ( ) ) ,
1001+ Some ( storage_path) ,
1002+ )
9971003 } else {
9981004 return Err ( AxumNope :: ResourceNotFound ) ;
9991005 }
@@ -1006,6 +1012,17 @@ pub(crate) async fn json_download_handler(
10061012 // Here we override it with the standard policy for build output.
10071013 response. extensions_mut ( ) . insert ( CachePolicy :: ForeverInCdn ) ;
10081014
1015+ // set content-disposition to attachment to trigger download in browsers
1016+ // For the attachment filename we can use just the filename without the path,
1017+ // since that already contains all the info.
1018+ let storage_path = updated_storage_path. unwrap_or ( storage_path) ;
1019+ let ( _, filename) = storage_path. rsplit_once ( '/' ) . unwrap_or ( ( "" , & storage_path) ) ;
1020+ response. headers_mut ( ) . insert (
1021+ CONTENT_DISPOSITION ,
1022+ generate_content_disposition_header ( filename)
1023+ . context ( "could not generate content-disposition header" ) ?,
1024+ ) ;
1025+
10091026 Ok ( response)
10101027}
10111028
@@ -3353,21 +3370,90 @@ mod test {
33533370 Ok ( ( ) )
33543371 }
33553372
3356- #[ test_case( "latest/json" , CompressionAlgorithm :: Zstd ) ]
3357- #[ test_case( "latest/json.gz" , CompressionAlgorithm :: Gzip ) ]
3358- #[ test_case( "0.1.0/json" , CompressionAlgorithm :: Zstd ) ]
3359- #[ test_case( "latest/json/latest" , CompressionAlgorithm :: Zstd ) ]
3360- #[ test_case( "latest/json/latest.gz" , CompressionAlgorithm :: Gzip ) ]
3361- #[ test_case( "latest/json/42" , CompressionAlgorithm :: Zstd ) ]
3362- #[ test_case( "latest/i686-pc-windows-msvc/json" , CompressionAlgorithm :: Zstd ) ]
3363- #[ test_case( "latest/i686-pc-windows-msvc/json.gz" , CompressionAlgorithm :: Gzip ) ]
3364- #[ test_case( "latest/i686-pc-windows-msvc/json/42" , CompressionAlgorithm :: Zstd ) ]
3365- #[ test_case( "latest/i686-pc-windows-msvc/json/42.gz" , CompressionAlgorithm :: Gzip ) ]
3366- #[ test_case( "latest/i686-pc-windows-msvc/json/42.zst" , CompressionAlgorithm :: Zstd ) ]
3373+ #[ test_case(
3374+ "latest/json" ,
3375+ CompressionAlgorithm :: Zstd ,
3376+ "x86_64-unknown-linux-gnu" ,
3377+ "latest" ,
3378+ "0.2.0"
3379+ ) ]
3380+ #[ test_case(
3381+ "latest/json.gz" ,
3382+ CompressionAlgorithm :: Gzip ,
3383+ "x86_64-unknown-linux-gnu" ,
3384+ "latest" ,
3385+ "0.2.0"
3386+ ) ]
3387+ #[ test_case(
3388+ "0.1.0/json" ,
3389+ CompressionAlgorithm :: Zstd ,
3390+ "x86_64-unknown-linux-gnu" ,
3391+ "latest" ,
3392+ "0.1.0"
3393+ ) ]
3394+ #[ test_case(
3395+ "latest/json/latest" ,
3396+ CompressionAlgorithm :: Zstd ,
3397+ "x86_64-unknown-linux-gnu" ,
3398+ "latest" ,
3399+ "0.2.0"
3400+ ) ]
3401+ #[ test_case(
3402+ "latest/json/latest.gz" ,
3403+ CompressionAlgorithm :: Gzip ,
3404+ "x86_64-unknown-linux-gnu" ,
3405+ "latest" ,
3406+ "0.2.0"
3407+ ) ]
3408+ #[ test_case(
3409+ "latest/json/42" ,
3410+ CompressionAlgorithm :: Zstd ,
3411+ "x86_64-unknown-linux-gnu" ,
3412+ "42" ,
3413+ "0.2.0"
3414+ ) ]
3415+ #[ test_case(
3416+ "latest/i686-pc-windows-msvc/json" ,
3417+ CompressionAlgorithm :: Zstd ,
3418+ "i686-pc-windows-msvc" ,
3419+ "latest" ,
3420+ "0.2.0"
3421+ ) ]
3422+ #[ test_case(
3423+ "latest/i686-pc-windows-msvc/json.gz" ,
3424+ CompressionAlgorithm :: Gzip ,
3425+ "i686-pc-windows-msvc" ,
3426+ "latest" ,
3427+ "0.2.0"
3428+ ) ]
3429+ #[ test_case(
3430+ "latest/i686-pc-windows-msvc/json/42" ,
3431+ CompressionAlgorithm :: Zstd ,
3432+ "i686-pc-windows-msvc" ,
3433+ "42" ,
3434+ "0.2.0"
3435+ ) ]
3436+ #[ test_case(
3437+ "latest/i686-pc-windows-msvc/json/42.gz" ,
3438+ CompressionAlgorithm :: Gzip ,
3439+ "i686-pc-windows-msvc" ,
3440+ "42" ,
3441+ "0.2.0"
3442+ ) ]
3443+ #[ test_case(
3444+ "latest/i686-pc-windows-msvc/json/42.zst" ,
3445+ CompressionAlgorithm :: Zstd ,
3446+ "i686-pc-windows-msvc" ,
3447+ "42" ,
3448+ "0.2.0"
3449+ ) ]
33673450 #[ tokio:: test( flavor = "multi_thread" ) ]
33683451 async fn json_download (
33693452 request_path_suffix : & str ,
33703453 expected_compression : CompressionAlgorithm ,
3454+ expected_target : & str ,
3455+ expected_format_version : & str ,
3456+ expected_version : & str ,
33713457 ) -> Result < ( ) > {
33723458 let env = TestEnvironment :: new ( ) . await ?;
33733459
@@ -3397,6 +3483,13 @@ mod test {
33973483 let resp = web
33983484 . assert_success_cached ( & path, CachePolicy :: ForeverInCdn , env. config ( ) )
33993485 . await ?;
3486+ assert_eq ! (
3487+ resp. headers( ) . get( CONTENT_DISPOSITION ) . unwrap( ) ,
3488+ & format!(
3489+ "attachment; filename=\" dummy_{expected_version}_{expected_target}_{expected_format_version}.json.{}\" " ,
3490+ expected_compression. file_extension( )
3491+ )
3492+ ) ;
34003493 web. assert_conditional_get ( & path, & resp) . await ?;
34013494
34023495 {
@@ -3469,6 +3562,10 @@ mod test {
34693562 let resp = web
34703563 . assert_success_cached ( & path, CachePolicy :: ForeverInCdn , env. config ( ) )
34713564 . await ?;
3565+ assert_eq ! (
3566+ resp. headers( ) . get( CONTENT_DISPOSITION ) . unwrap( ) ,
3567+ & format!( "attachment; filename=\" {NAME}_{VERSION}_{TARGET}_latest.json\" " ) ,
3568+ ) ;
34723569 web. assert_conditional_get ( & path, & resp) . await ?;
34733570 Ok ( ( ) )
34743571 }
@@ -3507,7 +3604,7 @@ mod test {
35073604 let response = web
35083605 . get ( & format ! ( "/crate/dummy/{request_path_suffix}" ) )
35093606 . await ?;
3510-
3607+ assert ! ( response . headers ( ) . get ( CONTENT_DISPOSITION ) . is_none ( ) ) ;
35113608 assert_eq ! ( response. status( ) , StatusCode :: NOT_FOUND ) ;
35123609 Ok ( ( ) )
35133610 }
0 commit comments