Skip to content

Commit

Permalink
Media handler #10869 (#10870)
Browse files Browse the repository at this point in the history
  • Loading branch information
anatol-sialitski committed Mar 5, 2025
1 parent f0f881e commit b5572dd
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 198 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ protected WebResponse doHandle( final WebRequest webRequest, final WebResponse w
doHandleLegacyHandler( webResponse, legacyAttachmentServiceEnabled, () -> attachmentHandler.handle( webRequest ) );
case "image" -> doHandleLegacyHandler( webResponse, legacyImageServiceEnabled, () -> imageHandler.handle( webRequest ) );
case "service" -> doHandleLegacyHandler( webResponse, legacyHttpServiceEnabled, () -> serviceHandler.handle( webRequest ) );
case "media" -> mediaHandler.handle( webRequest, webResponse );
case "media" -> mediaHandler.handle( webRequest );
case "error" -> errorHandler.handle( webRequest );
case "idprovider" -> identityHandler.handle( webRequest, webResponse );
case "asset" -> assetHandler.handle( webRequest );
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -40,6 +39,8 @@
import com.enonic.xp.media.MediaInfoService;
import com.enonic.xp.portal.PortalRequest;
import com.enonic.xp.portal.PortalResponse;
import com.enonic.xp.portal.impl.ContentResolver;
import com.enonic.xp.portal.impl.ContentResolverResult;
import com.enonic.xp.portal.impl.PortalConfig;
import com.enonic.xp.portal.impl.VirtualHostContextHelper;
import com.enonic.xp.portal.impl.handler.attachment.RangeRequestHelper;
Expand Down Expand Up @@ -76,8 +77,6 @@ public class MediaHandler

private static final EnumSet<HttpMethod> ALLOWED_METHODS = EnumSet.of( HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS );

private static final Predicate<WebRequest> IS_GET_HEAD_OPTIONS_METHOD = req -> ALLOWED_METHODS.contains( req.getMethod() );

private static final MediaType SVG_MEDIA_TYPE = MediaType.SVG_UTF_8.withoutParameters();

private static final int DEFAULT_BACKGROUND = 0xFFFFFF;
Expand All @@ -90,8 +89,6 @@ public class MediaHandler

private final MediaInfoService mediaInfoService;

private final DefaultContextPathVerifier defaultContextPathVerifier;

private volatile String privateCacheControlHeaderConfig;

private volatile String publicCacheControlHeaderConfig;
Expand All @@ -109,7 +106,6 @@ public MediaHandler( @Reference final ContentService contentService, @Reference
this.contentService = contentService;
this.imageService = imageService;
this.mediaInfoService = mediaInfoService;
this.defaultContextPathVerifier = new DefaultContextPathVerifier( contentService );
}

@Activate
Expand All @@ -126,16 +122,16 @@ public void activate( final PortalConfig config )
.collect( Collectors.toList() );
}

public WebResponse handle( final WebRequest webRequest, final WebResponse webResponse )
public WebResponse handle( final WebRequest webRequest )
throws Exception
{
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 ( !ALLOWED_METHODS.contains( webRequest.getMethod() ) )
{
throw new WebException( HttpStatus.METHOD_NOT_ALLOWED, String.format( "Method %s not allowed", webRequest.getMethod() ) );
}
Expand All @@ -145,6 +141,20 @@ public WebResponse handle( final WebRequest webRequest, final WebResponse webRes
return HandlerHelper.handleDefaultOptions( ALLOWED_METHODS );
}

if ( webRequest instanceof PortalRequest portalRequest )
{
if ( !portalRequest.isSiteBase() )
{
throw createNotFoundException();
}

final ContentResolverResult contentResolverResult = new ContentResolver( contentService ).resolve( portalRequest );
if ( !"/".equals( contentResolverResult.getSiteRelativePath() ) )
{
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" ) );
Expand All @@ -153,45 +163,41 @@ 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 );
verifyMediaScope( matcher.group( "context" ), webRequest, repositoryId, branch );
verifyAccessByBranch( branch );

return executeInContext( repositoryId, branch, () -> type.equals( "attachment" )
? doHandleAttachment( portalRequest, id, fingerprint, restPath )
: doHandleImage( portalRequest, id, fingerprint, restPath ) );
? doHandleAttachment( webRequest, id, fingerprint, restPath )
: doHandleImage( webRequest, id, fingerprint, restPath ) );
}

private void verifyMediaScope( final String projectContext, final RepositoryId repositoryId, final Branch branch,
final WebRequest webRequest )
private void verifyMediaScope( final String projectContext, final WebRequest webRequest, final RepositoryId repositoryId,
final Branch branch )
{
final String mediaServiceScope = VirtualHostContextHelper.getMediaServiceScope();
if ( mediaServiceScope != null )
if ( mediaServiceScope != null &&
MEDIA_SCOPE_DELIMITER_PATTERN.splitAsStream( mediaServiceScope ).map( String::trim ).noneMatch( projectContext::equals ) )
{
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 instanceof PortalRequest portalRequest &&
!( repositoryId.equals( portalRequest.getRepositoryId() ) && branch.equals( portalRequest.getBranch() ) ) )
{
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" );
}
}
throw createNotFoundException();
}
}

private PortalRequest createPortalRequest( final WebRequest webRequest, final RepositoryId repositoryId, final Branch branch )
private PortalResponse executeInContext( final RepositoryId repositoryId, final Branch branch, final Callable<PortalResponse> callable )
{
return ContextBuilder.copyOf( ContextAccessor.current() )
.repositoryId( repositoryId )
.branch( branch )
.build()
.callWith( callable );
}

private void verifyAccessByBranch( final Branch branch )
{
if ( ContentConstants.BRANCH_DRAFT.equals( branch ) )
{
Expand All @@ -201,30 +207,15 @@ private PortalRequest createPortalRequest( final WebRequest webRequest, final Re
throw WebException.forbidden( "You don't have permission to access this resource" );
}
}

PortalRequest portalRequest = webRequest instanceof PortalRequest ? (PortalRequest) webRequest : new PortalRequest( webRequest );
portalRequest.setRepositoryId( repositoryId );
portalRequest.setBranch( branch );
return portalRequest;
}

private PortalResponse executeInContext( final RepositoryId repositoryId, final Branch branch, final Callable<PortalResponse> callable )
{
return ContextBuilder.copyOf( ContextAccessor.current() )
.repositoryId( repositoryId )
.branch( branch )
.build()
.callWith( callable );
}

private PortalResponse doHandleImage( final PortalRequest portalRequest, final ContentId id, final String fingerprint,
final String restPath )
private PortalResponse doHandleImage( final WebRequest webRequest, final ContentId id, final String fingerprint, final String restPath )
throws Exception
{
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 );
Expand All @@ -239,10 +230,10 @@ private PortalResponse doHandleImage( final PortalRequest portalRequest, final C
throw WebException.notFound( String.format( "Attachment [%s] not found", content.getName() ) );
}

return resolveMedia( attachment, media, fingerprint, portalRequest, matcher, false );
return resolveMedia( attachment, media, fingerprint, webRequest, matcher, false );
}

private PortalResponse doHandleAttachment( final PortalRequest portalRequest, final ContentId id, final String fingerprint,
private PortalResponse doHandleAttachment( final WebRequest webRequest, final ContentId id, final String fingerprint,
final String restPath )
throws Exception
{
Expand All @@ -255,11 +246,11 @@ private PortalResponse doHandleAttachment( final PortalRequest portalRequest, fi
final Content content = getContent( id );
final Attachment attachment = resolveAttachment( content, matcher.group( "name" ) );

return resolveMedia( attachment, content, fingerprint, portalRequest, matcher, true );
return resolveMedia( attachment, content, fingerprint, webRequest, matcher, true );
}

private PortalResponse resolveMedia( final Attachment attachment, final Content content, final String fingerprint,
final PortalRequest portalRequest, final Matcher matcher, final boolean isAttachment )
final WebRequest webRequest, final Matcher matcher, final boolean isAttachment )
throws Exception
{
final BinaryReference binaryReference = attachment.getBinaryReference();
Expand Down Expand Up @@ -291,7 +282,7 @@ private PortalResponse resolveMedia( final Attachment attachment, final Content
else
{
final ScaleParams scaleParams = new ScaleParamsParser().parse( matcher.group( "scaleParams" ) );
body = transform( (Media) content, binaryReference, binary, contentType, scaleParams, portalRequest );
body = transform( (Media) content, binaryReference, binary, contentType, scaleParams, webRequest );
}

final Trace trace = Tracer.current();
Expand All @@ -305,12 +296,12 @@ private PortalResponse resolveMedia( final Attachment attachment, final Content

setContentEncodingHeader( portalResponse, isSvgz );
setContentSecurityPolicy( portalResponse, contentType );
setCacheControlHeader( portalResponse, content, fingerprint, portalRequest, attachment );
setCacheControlHeader( portalResponse, content, fingerprint, attachment );

if ( isAttachment )
{
setDispositionHeader( portalResponse, portalRequest, attachment.getName() );
new RangeRequestHelper().handleRangeRequest( portalRequest, portalResponse, body, contentType );
setDispositionHeader( portalResponse, webRequest, attachment.getName() );
new RangeRequestHelper().handleRangeRequest( webRequest, portalResponse, body, contentType );
}
else
{
Expand Down Expand Up @@ -346,12 +337,12 @@ private void setContentEncodingHeader( final PortalResponse.Builder portalRespon
}

private void setCacheControlHeader( final PortalResponse.Builder portalResponse, final Content content, final String fingerprint,
final PortalRequest portalRequest, final Attachment attachment )
final Attachment attachment )
{
if ( !nullToEmpty( fingerprint ).isBlank() )
{
final boolean isPublic = content.getPermissions().isAllowedFor( RoleKeys.EVERYONE, Permission.READ ) &&
ContentConstants.BRANCH_MASTER.equals( portalRequest.getBranch() );
ContentConstants.BRANCH_MASTER.equals( ContextAccessor.current().getBranch() );
final String cacheControlHeaderConfig = isPublic ? publicCacheControlHeaderConfig : privateCacheControlHeaderConfig;

if ( !nullToEmpty( cacheControlHeaderConfig ).isBlank() &&
Expand Down Expand Up @@ -380,9 +371,9 @@ private void setContentSecurityPolicy( final PortalResponse.Builder portalRespon
}
}

private void setDispositionHeader( final PortalResponse.Builder portalResponse, final PortalRequest portalRequest, final String name )
private void setDispositionHeader( final PortalResponse.Builder portalResponse, final WebRequest webRequest, final String name )
{
if ( HandlerHelper.getParameter( portalRequest, "download" ) != null )
if ( HandlerHelper.getParameter( webRequest, "download" ) != null )
{
portalResponse.header( HttpHeaders.CONTENT_DISPOSITION, contentDispositionAttachment( name ) );
}
Expand Down Expand Up @@ -429,12 +420,12 @@ private ByteSource getBinary( final ContentId id, final BinaryReference binaryRe
}

private ByteSource transform( final Media content, final BinaryReference binaryReference, final ByteSource binary,
final MediaType contentType, final ScaleParams scaleParams, final PortalRequest portalRequest )
final MediaType contentType, final ScaleParams scaleParams, final WebRequest webRequest )
throws IOException
{
final String qualityParam = HandlerHelper.getParameter( portalRequest, "quality" );
final String backgroundParam = HandlerHelper.getParameter( portalRequest, "background" );
final String filterParam = HandlerHelper.getParameter( portalRequest, "filter" );
final String qualityParam = HandlerHelper.getParameter( webRequest, "quality" );
final String backgroundParam = HandlerHelper.getParameter( webRequest, "background" );
final String filterParam = HandlerHelper.getParameter( webRequest, "filter" );

final ImageOrientation imageOrientation = Objects.requireNonNullElseGet( content.getOrientation(), () -> Objects.requireNonNullElse(
mediaInfoService.getImageOrientation( binary ), ImageOrientation.TopLeft ) );
Expand Down Expand Up @@ -470,4 +461,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" );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public void testHandleMedia()
when( webRequest.getRawPath() ).thenReturn( "/api/media/image/project:branch/id:fingerprint/name" );

final WebResponse webResponse = WebResponse.create().build();
when( mediaHandler.handle( any( WebRequest.class ), any( WebResponse.class ) ) ).thenReturn( webResponse );
when( mediaHandler.handle( any( WebRequest.class ) ) ).thenReturn( webResponse );

// test handle
assertEquals( webResponse, this.handler.doHandle( webRequest, webResponse, null ) );
Expand Down
Loading

0 comments on commit b5572dd

Please sign in to comment.