From fd2dab8d8e5c17d8d68a4d39d4d56958d36f0f32 Mon Sep 17 00:00:00 2001 From: Anatol Sialitski Date: Wed, 22 Jan 2025 16:14:16 +0100 Subject: [PATCH] Media handler #10869 --- .../xp/portal/impl/handler/MediaHandler.java | 50 ++++++++------ .../portal/impl/handler/MediaHandlerTest.java | 66 +++++++++---------- 2 files changed, 61 insertions(+), 55 deletions(-) diff --git a/modules/portal/portal-impl/src/main/java/com/enonic/xp/portal/impl/handler/MediaHandler.java b/modules/portal/portal-impl/src/main/java/com/enonic/xp/portal/impl/handler/MediaHandler.java index 235f1253406..6ae5c0c1d7d 100644 --- a/modules/portal/portal-impl/src/main/java/com/enonic/xp/portal/impl/handler/MediaHandler.java +++ b/modules/portal/portal-impl/src/main/java/com/enonic/xp/portal/impl/handler/MediaHandler.java @@ -76,7 +76,7 @@ public class MediaHandler private static final EnumSet ALLOWED_METHODS = EnumSet.of( HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS ); - private static final Predicate IS_GET_HEAD_OPTIONS_METHOD = req -> ALLOWED_METHODS.contains( req.getMethod() ); + private static final Predicate IS_ALLOWED_METHOD = req -> ALLOWED_METHODS.contains( req.getMethod() ); private static final MediaType SVG_MEDIA_TYPE = MediaType.SVG_UTF_8.withoutParameters(); @@ -132,10 +132,10 @@ public WebResponse handle( final WebRequest webRequest, final WebResponse webRes final Matcher matcher = PATTERN.matcher( Objects.requireNonNullElse( webRequest.getEndpointPath(), webRequest.getRawPath() ) ); if ( !matcher.matches() ) { - return PortalResponse.create( webResponse ).status( HttpStatus.NOT_FOUND ).build(); + throw createNotFoundException(); } - if ( !IS_GET_HEAD_OPTIONS_METHOD.test( webRequest ) ) + if ( !IS_ALLOWED_METHOD.test( webRequest ) ) { throw new WebException( HttpStatus.METHOD_NOT_ALLOWED, String.format( "Method %s not allowed", webRequest.getMethod() ) ); } @@ -145,6 +145,11 @@ public WebResponse handle( final WebRequest webRequest, final WebResponse webRes return HandlerHelper.handleDefaultOptions( ALLOWED_METHODS ); } + if ( !defaultContextPathVerifier.verify( webRequest ) ) + { + throw createNotFoundException(); + } + final RepositoryId repositoryId = HandlerHelper.resolveRepositoryId( ProjectConstants.PROJECT_REPO_ID_PREFIX + matcher.group( "project" ) ); final Branch branch = HandlerHelper.resolveBranch( Objects.requireNonNullElse( matcher.group( "branch" ), "master" ) ); @@ -153,11 +158,6 @@ public WebResponse handle( final WebRequest webRequest, final WebResponse webRes final String fingerprint = matcher.group( "fingerprint" ); final String restPath = matcher.group( "restPath" ); - if ( !defaultContextPathVerifier.verify( webRequest ) ) - { - throw WebException.notFound( "Not a valid media url pattern" ); - } - verifyMediaScope( matcher.group( "context" ), repositoryId, branch, webRequest ); final PortalRequest portalRequest = createPortalRequest( webRequest, repositoryId, branch ); @@ -175,19 +175,26 @@ private void verifyMediaScope( final String projectContext, final RepositoryId r { if ( MEDIA_SCOPE_DELIMITER_PATTERN.splitAsStream( mediaServiceScope ).map( String::trim ).noneMatch( projectContext::equals ) ) { - throw WebException.notFound( "Not a valid media url pattern" ); + throw createNotFoundException(); } } - else + + if ( webRequest.getRawPath().startsWith( "/api/" ) ) { - if ( webRequest instanceof final PortalRequest portalRequest ) - { - if ( portalRequest.isSiteBase() && - !( repositoryId.equals( portalRequest.getRepositoryId() ) && branch.equals( portalRequest.getBranch() ) ) ) - { - throw WebException.notFound( "Not a valid media url pattern" ); - } - } + return; + } + + if ( !( webRequest.getRawPath().startsWith( "/site/" ) || webRequest.getRawPath().startsWith( "/admin/site/" ) ) ) + { + throw createNotFoundException(); + } + + final PortalRequest portalRequest = + webRequest instanceof PortalRequest ? (PortalRequest) webRequest : new PortalRequest( webRequest ); + + if ( !( repositoryId.equals( portalRequest.getRepositoryId() ) && branch.equals( portalRequest.getBranch() ) ) ) + { + throw createNotFoundException(); } } @@ -224,7 +231,7 @@ private PortalResponse doHandleImage( final PortalRequest portalRequest, final C final Matcher matcher = IMAGE_REST_PATH_PATTERN.matcher( restPath ); if ( !matcher.matches() ) { - throw WebException.notFound( "Not a valid image url pattern" ); + throw createNotFoundException(); } final Content content = getContent( id ); @@ -470,4 +477,9 @@ private ByteSource transform( final Media content, final BinaryReference binaryR throw new WebException( HttpStatus.TOO_MANY_REQUESTS, "Try again later", e ); } } + + private WebException createNotFoundException() + { + return WebException.notFound( "Not a valid media url pattern" ); + } } diff --git a/modules/portal/portal-impl/src/test/java/com/enonic/xp/portal/impl/handler/MediaHandlerTest.java b/modules/portal/portal-impl/src/test/java/com/enonic/xp/portal/impl/handler/MediaHandlerTest.java index 54efa1b1fa8..65895a32e45 100644 --- a/modules/portal/portal-impl/src/test/java/com/enonic/xp/portal/impl/handler/MediaHandlerTest.java +++ b/modules/portal/portal-impl/src/test/java/com/enonic/xp/portal/impl/handler/MediaHandlerTest.java @@ -109,18 +109,16 @@ private void setupMedia() @Test void testInvalidUrl() - throws Exception { this.request.setEndpointPath( null ); this.request.setRawPath( "/api/attachment/myproject/123456:ec25d6e4126c7064f82aaab8b34693fc/logo.png" ); - final PortalResponse res = (PortalResponse) this.handler.handle( this.request, PortalResponse.create().build() ); - assertNotNull( res ); - assertEquals( HttpStatus.NOT_FOUND, res.getStatus() ); + WebException ex = assertThrows( WebException.class, () -> this.handler.handle( this.request, WebResponse.create().build() ) ); + assertEquals( HttpStatus.NOT_FOUND, ex.getStatus() ); } @Test - public void testAttachment() + void testAttachment() throws Exception { setupMedia(); @@ -138,41 +136,31 @@ public void testAttachment() } @Test - public void testAttachmentForEndpointOnAdmin() - throws Exception + void testAttachmentForEndpointOnAdmin() { setupMedia(); this.request.setEndpointPath( "/_/media/attachment/myproject/123456:ec25d6e4126c7064f82aaab8b34693fc/logo.png" ); - this.request.setRawPath( "/admin/_/media/attachment/myproject/123456:ec25d6e4126c7064f82aaab8b34693fc/logo.png" ); + this.request.setRawPath( "/admin/app/toolName/_/media/attachment/myproject/123456:ec25d6e4126c7064f82aaab8b34693fc/logo.png" ); - final PortalResponse res = (PortalResponse) this.handler.handle( this.request, PortalResponse.create().build() ); - assertNotNull( res ); - assertEquals( HttpStatus.OK, res.getStatus() ); - assertEquals( MediaType.PNG, res.getContentType() ); - assertNull( res.getHeaders().get( "Content-Disposition" ) ); - assertSame( this.mediaBytes, res.getBody() ); + WebException ex = assertThrows( WebException.class, () -> this.handler.handle( this.request, WebResponse.create().build() ) ); + assertEquals( HttpStatus.NOT_FOUND, ex.getStatus() ); } @Test - public void testAttachmentForEndpointOnWebApp() - throws Exception + void testAttachmentForEndpointOnWebApp() { setupMedia(); this.request.setEndpointPath( "/_/media/attachment/myproject/123456/logo.png" ); this.request.setRawPath( "/webapp/com.enonic.app.mywebapp/_/media/attachment/myproject/123456/logo.png" ); - final PortalResponse res = (PortalResponse) this.handler.handle( this.request, PortalResponse.create().build() ); - assertNotNull( res ); - assertEquals( HttpStatus.OK, res.getStatus() ); - assertEquals( MediaType.PNG, res.getContentType() ); - assertNull( res.getHeaders().get( "Content-Disposition" ) ); - assertSame( this.mediaBytes, res.getBody() ); + WebException ex = assertThrows( WebException.class, () -> this.handler.handle( this.request, WebResponse.create().build() ) ); + assertEquals( HttpStatus.NOT_FOUND, ex.getStatus() ); } @Test - public void testInvalidContextPath() + void testInvalidContextPath() { setupMedia(); @@ -185,7 +173,7 @@ public void testInvalidContextPath() } @Test - public void testAttachmentForEndpointOnSite() + void testAttachmentForEndpointOnSite() throws Exception { setupMedia(); @@ -213,7 +201,7 @@ public void testAttachmentForEndpointOnSite() } @Test - public void testMediaEndpointDoesNotAllowedIfSiteContextDoesNotMatch() + void testMediaEndpointDoesNotAllowedIfSiteContextDoesNotMatch() { setupMedia(); @@ -237,7 +225,7 @@ public void testMediaEndpointDoesNotAllowedIfSiteContextDoesNotMatch() } @Test - public void testAttachmentDownload() + void testAttachmentDownload() throws Exception { setupMedia(); @@ -263,7 +251,7 @@ public void testAttachmentDownload() } @Test - public void testAttachmentDraftBranchForNotAuthorizedUser() + void testAttachmentDraftBranchForNotAuthorizedUser() { this.request.setRawPath( "/api/media/attachment/myproject:draft/123456/logo.png" ); this.request.setBranch( ContentConstants.BRANCH_DRAFT ); @@ -275,11 +263,12 @@ public void testAttachmentDraftBranchForNotAuthorizedUser() } @Test - public void testImage() + void testImage() throws Exception { setupContent(); + this.request.setEndpointPath( null ); this.request.setRawPath( "/api/media/image/myproject/123456/scale-100-100/image-name.jpg" ); WebResponse res = this.handler.handle( this.request, PortalResponse.create().build() ); @@ -298,9 +287,10 @@ public void testImage() } @Test - public void testOptions() + void testOptions() throws Exception { + this.request.setEndpointPath( null ); this.request.setRawPath( "/api/media/attachment/myproject:draft/123456/logo.png" ); this.request.setMethod( HttpMethod.OPTIONS ); @@ -314,6 +304,7 @@ public void testOptions() void testHandleMethodNotAllowed() { this.request.setMethod( HttpMethod.DELETE ); + this.request.setEndpointPath( null ); this.request.setRawPath( "/api/media/attachment/myproject:draft/123456/logo.png" ); WebException ex = assertThrows( WebException.class, () -> this.handler.handle( this.request, WebResponse.create().build() ) ); @@ -373,18 +364,21 @@ void testMediaScope() webResponse = this.handler.handle( this.request, PortalResponse.create().build() ); assertEquals( HttpStatus.OK, webResponse.getStatus() ); - this.request.setEndpointPath( "/_/media/image/myproject/123456/scale-100-100/image-name.jpg" ); - this.request.setRawPath( "/admin/_/media/image/myproject/123456/scale-100-100/image-name.jpg" ); + this.request.setEndpointPath( "/_/media/image/myproject:draft/123456/scale-100-100/image-name.jpg" ); + this.request.setRawPath( + "/admin/site/preview/myproject/draft/_/media/image/myproject:draft/123456/scale-100-100/image-name.jpg" ); webResponse = this.handler.handle( this.request, PortalResponse.create().build() ); assertEquals( HttpStatus.OK, webResponse.getStatus() ); this.request.setEndpointPath( "/_/media/image/myproject:draft/123456/scale-100-100/image-name.jpg" ); - this.request.setRawPath( "/admin/_/media/image/myproject:draft/123456/scale-100-100/image-name.jpg" ); + this.request.setRawPath( + "/admin/site/preview/myproject/master/_/media/image/myproject:draft/123456/scale-100-100/image-name.jpg" ); webResponse = this.handler.handle( this.request, PortalResponse.create().build() ); assertEquals( HttpStatus.OK, webResponse.getStatus() ); this.request.setEndpointPath( "/_/media/image/unknown:draft/123456/scale-100-100/image-name.jpg" ); - this.request.setRawPath( "/admin/_/media/image/unknown:draft/123456/scale-100-100/image-name.jpg" ); + this.request.setRawPath( + "/admin/site/preview/unknown/draft/_/media/image/unknown:draft/123456/scale-100-100/image-name.jpg" ); WebException ex = assertThrows( WebException.class, () -> this.handler.handle( this.request, PortalResponse.create().build() ) ); assertEquals( HttpStatus.NOT_FOUND, ex.getStatus() ); @@ -404,7 +398,7 @@ void svgzImage() when( mediaInfoService.getImageOrientation( any( ByteSource.class ) ) ).thenReturn( ImageOrientation.LeftBottom ); this.request.setEndpointPath( "/_/media/image/myproject/123456/full/image-name.svgz" ); - this.request.setRawPath( "/admin/_/media/image/myproject/123456/full/image-name.svgz" ); + this.request.setRawPath( "/admin/site/preview/myproject/master/_/media/image/myproject/123456/full/image-name.svgz" ); final WebResponse res = this.handler.handle( this.request, PortalResponse.create().build() ); assertNotNull( res ); @@ -417,13 +411,13 @@ void svgzImage() } @Test - void gifImage() + void testGifImage() throws Exception { setupContentGif(); this.request.setEndpointPath( "/_/media/image/myproject/123456/full/image-name.gif" ); - this.request.setRawPath( "/admin/_/media/image/myproject/123456/full/image-name.svgz" ); + this.request.setRawPath( "/site/myproject/master/sitepath/_/media/image/myproject/123456/full/image-name.gif" ); final WebResponse res = this.handler.handle( this.request, PortalResponse.create().build() ); assertNotNull( res );