From dfd6b5bee29e98b8dbdcac4782647003b62c6a97 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 22 May 2024 09:34:49 +0200 Subject: [PATCH] Dynamic Media Support: Support for PNG with alpha channel (#51) Append &fmt=png-alpha for PNG assets to ensure that the alpha channel is preserved in the Dynamic Media rendition --- changes.xml | 6 ++++++ .../impl/dynamicmedia/DynamicMediaPath.java | 13 +++++++++++++ ...erImplEnd2EndDynamicMediaSmartCropTest.java | 18 +++++++++++++++++- ...lImageFileTypesEnd2EndDynamicMediaTest.java | 6 +++--- .../dynamicmedia/DynamicMediaPathTest.java | 11 +++++++++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/changes.xml b/changes.xml index d92fb011..54fc5bc5 100644 --- a/changes.xml +++ b/changes.xml @@ -23,6 +23,12 @@ xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd"> + + + Dynamic Media Support: Append fmt=png-alpha for PNG assets to ensure that the alpha channel is preserved in the Dynamic Media rendition. + + + MediaFileServlet: Use Content-Security-Policy instead of Content-Disposition header to prevent XSS attacks in SVG files. diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPath.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPath.java index 67359e8e..467e4723 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPath.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPath.java @@ -32,6 +32,7 @@ import io.wcm.handler.media.Dimension; import io.wcm.handler.media.format.Ratio; import io.wcm.handler.mediasource.dam.impl.DamContext; +import io.wcm.wcm.commons.contenttype.ContentType; /** * Build part of dynamic media/scene7 URL to render renditions. @@ -130,6 +131,10 @@ private DynamicMediaPath() { .append("hei=").append(dimension.getHeight()).append("&") // cropping/width/height is pre-calculated to fit with original ratio, make sure there are no 1px background lines visible .append("fit=stretch"); + if (isPNG(damContext)) { + // if original image is PNG image, make sure alpha channel is preserved + result.append("&fmt=png-alpha"); + } logResult(damContext, result); return result.toString(); } @@ -146,6 +151,10 @@ private DynamicMediaPath() { .append("hei=").append(dimension.getHeight()).append("&") // cropping/width/height is pre-calculated to fit with original ratio, make sure there are no 1px background lines visible .append("fit=stretch"); + if (isPNG(damContext)) { + // if original image is PNG image, make sure alpha channel is preserved + result.append("&fmt=png-alpha"); + } logResult(damContext, result); return result.toString(); } @@ -195,4 +204,8 @@ private static String encodeDynamicMediaObject(@NotNull DamContext damContext) { return StringUtils.join(pathParts, "/"); } + private static boolean isPNG(@NotNull DamContext damContext) { + return StringUtils.equals(damContext.getAsset().getMimeType(), ContentType.PNG); + } + } diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplEnd2EndDynamicMediaSmartCropTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplEnd2EndDynamicMediaSmartCropTest.java index d9d38928..062c8c00 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplEnd2EndDynamicMediaSmartCropTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplEnd2EndDynamicMediaSmartCropTest.java @@ -65,6 +65,7 @@ class MediaHandlerImplEnd2EndDynamicMediaSmartCropTest { private final AemContext context = AppAemContext.newAemContext(); + private Resource assetFolder; private Asset asset; private MediaHandler mediaHandler; @@ -74,7 +75,7 @@ void setUp() { PN_CROP_TYPE, CROP_TYPE_SMART, PN_BANNER, "16-10,16,10|4-3,40,30"); - Resource assetFolder = context.create().resource("/content/dam/folder1"); + assetFolder = context.create().resource("/content/dam/folder1"); context.create().resource(assetFolder, JCR_CONTENT, DamConstants.IMAGE_PROFILE, profile1.getPath()); asset = context.create().asset(assetFolder.getPath() + "/test.jpg", 160, 100, ContentType.JPEG, @@ -156,6 +157,21 @@ void testValidSmartCroppedRenditionOnlyRatio_MatchingOriginalRatio() { assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A16-10?wid=120&hei=75&fit=stretch", renditions.get(0).getUrl()); } + @Test + void testValidSmartCroppedRendition_OnlyRatio_PNG() { + // create PNG asset instead of JPEG, skip the extra renditions + asset = context.create().asset(assetFolder.getPath() + "/test.png", 160, 100, ContentType.PNG, + Scene7Constants.PN_S7_FILE, "DummyFolder/test"); + + Media media = getMediaWithRatio(RATIO_4_3); + assertTrue(media.isValid()); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(1, renditions.size()); + assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A4-3?wid=133&hei=100&fit=stretch&fmt=png-alpha", renditions.get(0).getUrl()); + } + + private Media getMediaWithWidths(MediaFormat mediaFormat, long... widths) { return mediaHandler.get(asset.getPath()) .pictureSource(new PictureSource(mediaFormat).widths(widths)) diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest.java index c3278b08..1d18d13c 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest.java @@ -187,7 +187,7 @@ void testFileUpload_GIF_AutoCrop() { void testAsset_PNG_Original() { Asset asset = createSampleAsset("/filetype/sample.png", ContentType.PNG); buildAssertMedia(asset, 100, 50, - "https://dummy.scene7.com/is/image/DummyFolder/sample.png?wid=100&hei=50&fit=stretch", + "https://dummy.scene7.com/is/image/DummyFolder/sample.png?wid=100&hei=50&fit=stretch&fmt=png-alpha", ContentType.PNG); } @@ -196,7 +196,7 @@ void testAsset_PNG_Original() { void testAsset_PNG_Rescale() { Asset asset = createSampleAsset("/filetype/sample.png", ContentType.PNG); buildAssertMedia_Rescale(asset, 80, 40, - "https://dummy.scene7.com/is/image/DummyFolder/sample.png?wid=80&hei=40&fit=stretch", + "https://dummy.scene7.com/is/image/DummyFolder/sample.png?wid=80&hei=40&fit=stretch&fmt=png-alpha", ContentType.PNG); } @@ -205,7 +205,7 @@ void testAsset_PNG_Rescale() { void testAsset_PNG_AutoCrop() { Asset asset = createSampleAsset("/filetype/sample.png", ContentType.PNG); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://dummy.scene7.com/is/image/DummyFolder/sample.png?crop=25,0,50,50&wid=50&hei=50&fit=stretch", + "https://dummy.scene7.com/is/image/DummyFolder/sample.png?crop=25,0,50,50&wid=50&hei=50&fit=stretch&fmt=png-alpha", ContentType.PNG); } diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPathTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPathTest.java index 86ee7fe6..ddd8680a 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPathTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPathTest.java @@ -174,4 +174,15 @@ void testBuildImage_SpecialChars() { assertEquals("/is/content/DummyFolder/test%20with%20spaces%20%C3%A4%C3%B6%C3%BC%C3%9F%E2%82%AC", result); } + @Test + void testCrop_PNG() { + asset = context.create().asset(assetFolder.getPath() + "/test.png", 50, 30, ContentType.PNG, + Scene7Constants.PN_S7_FILE, "DummyFolder/test"); + damContext = new DamContext(asset, new MediaArgs(), mediaHandlerConfig, + dynamicMediaSupportService, webOptimizedImageDeliveryService, context.request()); + + String result = DynamicMediaPath.buildImage(damContext, 30, 20, new CropDimension(5, 2, 10, 8), null); + assertEquals("/is/image/DummyFolder/test?crop=5,2,10,8&wid=30&hei=20&fit=stretch&fmt=png-alpha", result); + } + }