@@ -673,6 +673,11 @@ fn build_asset_proxy_target_url(
673673 Ok ( target_url)
674674}
675675
676+ fn asset_path_skips_image_optimizer ( target_url : & url:: Url ) -> bool {
677+ let lower_path = target_url. path ( ) . to_ascii_lowercase ( ) ;
678+ lower_path. ends_with ( ".svg" ) || lower_path. ends_with ( ".svgz" )
679+ }
680+
676681fn asset_origin_host_header (
677682 target_url : & url:: Url ,
678683) -> Result < HeaderValue , Report < TrustedServerError > > {
@@ -949,8 +954,16 @@ pub async fn handle_asset_proxy_request(
949954) -> Result < AssetProxyResponse , Report < TrustedServerError > > {
950955 let incoming_query = req. get_query_str ( ) . unwrap_or ( "" ) ;
951956 let mut target_url = build_asset_proxy_target_url ( route, req. get_path ( ) , incoming_query) ?;
952- let image_optimizer =
953- crate :: asset_image_optimizer:: options_for_asset_request ( settings, route, incoming_query) ?;
957+ let skip_image_optimizer = asset_path_skips_image_optimizer ( & target_url) ;
958+ let image_optimizer = if skip_image_optimizer {
959+ log:: debug!(
960+ "Skipping Image Optimizer for unsupported SVG asset path: {}" ,
961+ target_url. path( )
962+ ) ;
963+ None
964+ } else {
965+ crate :: asset_image_optimizer:: options_for_asset_request ( settings, route, incoming_query) ?
966+ } ;
954967
955968 if route. origin_query_policy ( ) == OriginQueryPolicy :: Strip {
956969 target_url. set_query ( None ) ;
@@ -1791,7 +1804,7 @@ mod tests {
17911804 use std:: sync:: { Arc , Mutex } ;
17921805
17931806 use super :: {
1794- asset_origin_host_header, build_asset_proxy_target_url,
1807+ asset_origin_host_header, asset_path_skips_image_optimizer , build_asset_proxy_target_url,
17951808 clear_s3_credentials_cache_for_tests, handle_asset_proxy_request, handle_first_party_click,
17961809 handle_first_party_proxy, handle_first_party_proxy_rebuild, handle_first_party_proxy_sign,
17971810 is_host_allowed, proxy_request, rebuild_response_with_body,
@@ -1810,7 +1823,7 @@ mod tests {
18101823 use crate :: settings:: {
18111824 AssetImageOptimizerConfig , AssetOriginAuth , ImageOptimizerAspectRatioConfig ,
18121825 ImageOptimizerCropOffsetsConfig , ImageOptimizerProfileSet , ImageOptimizerSettings ,
1813- OriginQueryPolicy , ProxyAssetRoute , S3SigV4AuthConfig ,
1826+ OriginQueryPolicy , ProxyAssetRoute , S3SigV4AuthConfig , UnknownProfilePolicy ,
18141827 } ;
18151828 use crate :: test_support:: tests:: create_test_settings;
18161829 use crate :: {
@@ -2872,6 +2885,36 @@ mod tests {
28722885 ) ;
28732886 }
28742887
2888+ #[ test]
2889+ fn asset_path_skips_image_optimizer_for_svg_extensions ( ) {
2890+ for url in [
2891+ "https://assets.example.com/.images/logo.svg" ,
2892+ "https://assets.example.com/.images/LOGO.SVG" ,
2893+ "https://assets.example.com/.images/icon.svgz" ,
2894+ ] {
2895+ let target_url = url:: Url :: parse ( url) . expect ( "should parse target URL" ) ;
2896+ assert ! (
2897+ asset_path_skips_image_optimizer( & target_url) ,
2898+ "should skip Image Optimizer for {url}"
2899+ ) ;
2900+ }
2901+ }
2902+
2903+ #[ test]
2904+ fn asset_path_uses_image_optimizer_for_raster_extensions ( ) {
2905+ for url in [
2906+ "https://assets.example.com/.images/photo.jpg" ,
2907+ "https://assets.example.com/.images/photo.png" ,
2908+ "https://assets.example.com/.images/photo.webp" ,
2909+ ] {
2910+ let target_url = url:: Url :: parse ( url) . expect ( "should parse target URL" ) ;
2911+ assert ! (
2912+ !asset_path_skips_image_optimizer( & target_url) ,
2913+ "should allow Image Optimizer for {url}"
2914+ ) ;
2915+ }
2916+ }
2917+
28752918 #[ test]
28762919 fn asset_origin_host_header_omits_standard_port ( ) {
28772920 let target_url = url:: Url :: parse ( "https://assets.example.com/.images/foo.jpg" )
@@ -3299,6 +3342,126 @@ mod tests {
32993342 assert_eq ! ( ( crop. offset_x, crop. offset_y) , ( Some ( 70 ) , Some ( 50 ) ) ) ;
33003343 }
33013344
3345+ #[ tokio:: test]
3346+ async fn handle_asset_proxy_request_skips_image_optimizer_for_svg_assets ( ) {
3347+ let stub = Arc :: new ( StubHttpClient :: new ( ) ) ;
3348+ stub. push_response ( 200 , b"ok" . to_vec ( ) ) ;
3349+ let services = build_services_with_http_client (
3350+ Arc :: clone ( & stub) as Arc < dyn crate :: platform:: PlatformHttpClient >
3351+ ) ;
3352+ let mut settings = create_test_settings ( ) ;
3353+ let mut profile_set = test_profile_set ( ) ;
3354+ profile_set. unknown_profile = UnknownProfilePolicy :: Reject ;
3355+ settings. image_optimizer = ImageOptimizerSettings {
3356+ profile_sets : HashMap :: from ( [ ( "default_images" . to_string ( ) , profile_set) ] ) ,
3357+ } ;
3358+ let req = Request :: new (
3359+ Method :: GET ,
3360+ "https://www.example.com/.images/logo.SVG?profile=unknown&ar=1-1" ,
3361+ ) ;
3362+ let mut route = ProxyAssetRoute :: new ( "/.images/" , "https://assets.example.com" ) ;
3363+ route. image_optimizer = Some ( AssetImageOptimizerConfig {
3364+ enabled : true ,
3365+ region : "us_east" . to_string ( ) ,
3366+ profile_set : "default_images" . to_string ( ) ,
3367+ origin_query : None ,
3368+ } ) ;
3369+
3370+ handle_asset_proxy_request ( & settings, & services, req, & route)
3371+ . await
3372+ . expect ( "should proxy SVG asset without Image Optimizer profile parsing" ) ;
3373+
3374+ assert_eq ! (
3375+ stub. recorded_request_uris( ) ,
3376+ vec![ "https://assets.example.com/.images/logo.SVG" ] ,
3377+ "should still strip profile-table query from SVG origin requests"
3378+ ) ;
3379+ assert_eq ! (
3380+ stub. recorded_image_optimizer_options( ) ,
3381+ vec![ None ] ,
3382+ "SVG assets should bypass Image Optimizer metadata"
3383+ ) ;
3384+ }
3385+
3386+ #[ tokio:: test]
3387+ async fn handle_asset_proxy_request_skips_image_optimizer_for_rewritten_svg_assets ( ) {
3388+ let stub = Arc :: new ( StubHttpClient :: new ( ) ) ;
3389+ stub. push_response ( 200 , b"ok" . to_vec ( ) ) ;
3390+ let services = build_services_with_http_client (
3391+ Arc :: clone ( & stub) as Arc < dyn crate :: platform:: PlatformHttpClient >
3392+ ) ;
3393+ let mut settings = create_test_settings ( ) ;
3394+ settings. image_optimizer = ImageOptimizerSettings {
3395+ profile_sets : HashMap :: from ( [ ( "default_images" . to_string ( ) , test_profile_set ( ) ) ] ) ,
3396+ } ;
3397+ let req = Request :: new (
3398+ Method :: GET ,
3399+ "https://www.example.com/.image/options/id/logo.svg?profile=medium" ,
3400+ ) ;
3401+ let mut route = ProxyAssetRoute :: new ( "/.image/" , "https://assets.example.com" ) ;
3402+ route. path_pattern = Some ( r"^/\.image/(.*)/[^/]+\.([^/.]+)$" . to_string ( ) ) ;
3403+ route. target_path = Some ( "/image/upload/$1.$2" . to_string ( ) ) ;
3404+ route. image_optimizer = Some ( AssetImageOptimizerConfig {
3405+ enabled : true ,
3406+ region : "us_east" . to_string ( ) ,
3407+ profile_set : "default_images" . to_string ( ) ,
3408+ origin_query : None ,
3409+ } ) ;
3410+
3411+ handle_asset_proxy_request ( & settings, & services, req, & route)
3412+ . await
3413+ . expect ( "should proxy rewritten SVG asset without Image Optimizer metadata" ) ;
3414+
3415+ assert_eq ! (
3416+ stub. recorded_request_uris( ) ,
3417+ vec![ "https://assets.example.com/image/upload/options/id.svg" ] ,
3418+ "should detect SVG after route path rewriting"
3419+ ) ;
3420+ assert_eq ! (
3421+ stub. recorded_image_optimizer_options( ) ,
3422+ vec![ None ] ,
3423+ "rewritten SVG assets should bypass Image Optimizer metadata"
3424+ ) ;
3425+ }
3426+
3427+ #[ tokio:: test]
3428+ async fn handle_asset_proxy_request_skips_s3_preflight_for_svg_image_optimizer_routes ( ) {
3429+ let stub = Arc :: new ( StubHttpClient :: new ( ) ) ;
3430+ stub. push_response ( 200 , b"raw-svg" . to_vec ( ) ) ;
3431+ let services = build_services_with_secret_and_http_client (
3432+ HashMapSecretStore :: new ( test_s3_secrets ( ) ) ,
3433+ Arc :: clone ( & stub) as Arc < dyn crate :: platform:: PlatformHttpClient > ,
3434+ ) ;
3435+ let settings = create_test_settings ( ) ;
3436+ let req = Request :: new (
3437+ Method :: GET ,
3438+ "https://www.example.com/.images/logo.svg?profile=medium" ,
3439+ ) ;
3440+ let route = test_s3_image_optimizer_route ( ) ;
3441+
3442+ let mut response = handle_asset_proxy_request ( & settings, & services, req, & route)
3443+ . await
3444+ . expect ( "should proxy raw SVG asset through S3 route" )
3445+ . into_response ( ) ;
3446+
3447+ assert_eq ! ( response. take_body_str( ) , "raw-svg" ) ;
3448+ assert_eq ! (
3449+ stub. recorded_request_methods( ) ,
3450+ vec![ "GET" ] ,
3451+ "SVG IO bypass should not add an S3 preflight"
3452+ ) ;
3453+ assert_eq ! (
3454+ stub. recorded_request_uris( ) ,
3455+ vec![ "https://examplebucket.s3.us-east-1.amazonaws.com/.images/logo.svg" ] ,
3456+ "SVG IO bypass should still strip the transform query"
3457+ ) ;
3458+ assert_eq ! (
3459+ stub. recorded_image_optimizer_options( ) ,
3460+ vec![ None ] ,
3461+ "SVG IO bypass should not attach Image Optimizer metadata"
3462+ ) ;
3463+ }
3464+
33023465 #[ tokio:: test]
33033466 async fn handle_asset_proxy_request_preflights_s3_before_image_optimizer ( ) {
33043467 let stub = Arc :: new ( StubHttpClient :: new ( ) ) ;
@@ -3361,7 +3524,7 @@ mod tests {
33613524 stub. push_response ( 404 , Vec :: new ( ) ) ;
33623525 stub. push_response_with_headers (
33633526 404 ,
3364- br#"<Error><Code>NoSuchKey</Code><Key>image/upload/missing.svg </Key></Error>"# . to_vec ( ) ,
3527+ br#"<Error><Code>NoSuchKey</Code><Key>image/upload/missing.jpg </Key></Error>"# . to_vec ( ) ,
33653528 vec ! [
33663529 ( header:: CONTENT_TYPE . as_str( ) , "application/xml" ) ,
33673530 ( header:: CACHE_CONTROL . as_str( ) , "public, max-age=3600" ) ,
@@ -3378,7 +3541,7 @@ mod tests {
33783541 } ;
33793542 let req = Request :: new (
33803543 Method :: GET ,
3381- "https://www.example.com/.images/missing.svg ?profile=medium" ,
3544+ "https://www.example.com/.images/missing.jpg ?profile=medium" ,
33823545 ) ;
33833546 let route = test_s3_image_optimizer_route ( ) ;
33843547
@@ -3405,7 +3568,7 @@ mod tests {
34053568 let body = response. take_body_str ( ) ;
34063569 assert ! ( body. contains( "NoSuchKey" ) , "should return S3 error body" ) ;
34073570 assert ! (
3408- body. contains( "image/upload/missing.svg " ) ,
3571+ body. contains( "image/upload/missing.jpg " ) ,
34093572 "should expose the missing S3 key"
34103573 ) ;
34113574 assert_eq ! (
0 commit comments