/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.web.portal.attachment;
import java.io.File;
import java.io.IOException;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.enonic.cms.framework.blob.BlobRecord;
import com.enonic.cms.framework.util.HttpServletRangeUtil;
import com.enonic.cms.framework.util.HttpServletUtil;
import com.enonic.cms.framework.util.MimeTypeResolver;
import com.enonic.cms.core.Attribute;
import com.enonic.cms.core.Path;
import com.enonic.cms.core.content.ContentEntity;
import com.enonic.cms.core.content.ContentVersionEntity;
import com.enonic.cms.core.content.access.ContentAccessResolver;
import com.enonic.cms.core.content.binary.AttachmentNotFoundException;
import com.enonic.cms.core.content.binary.AttachmentRequest;
import com.enonic.cms.core.content.binary.BinaryDataEntity;
import com.enonic.cms.core.content.binary.ContentBinaryDataEntity;
import com.enonic.cms.core.portal.PathRequiresAuthenticationException;
import com.enonic.cms.core.portal.ReservedLocalPaths;
import com.enonic.cms.core.portal.livetrace.AttachmentRequestTrace;
import com.enonic.cms.core.portal.livetrace.AttachmentRequestTracer;
import com.enonic.cms.core.portal.livetrace.PortalRequestTrace;
import com.enonic.cms.core.portal.livetrace.PortalRequestTracer;
import com.enonic.cms.core.preview.PreviewContext;
import com.enonic.cms.core.security.user.UserEntity;
import com.enonic.cms.core.structure.SiteEntity;
import com.enonic.cms.core.structure.SitePath;
import com.enonic.cms.core.structure.SiteProperties;
import com.enonic.cms.core.structure.SitePropertyNames;
import com.enonic.cms.core.structure.menuitem.MenuItemEntity;
import com.enonic.cms.store.dao.BinaryDataDao;
import com.enonic.cms.web.portal.PortalWebContext;
import com.enonic.cms.web.portal.handler.WebHandlerBase;
@Component
public final class AttachmentHandler
extends WebHandlerBase
{
private BinaryDataDao binaryDataDao;
private AttachmentRequestResolverImpl attachmentRequestResolver;
private MimeTypeResolver mimeTypeResolver;
@PostConstruct
public void init()
{
this.attachmentRequestResolver = new AttachmentRequestResolverImpl( this.contentDao );
}
@Override
protected boolean canHandle( final Path localPath )
{
return localPath.containsSubPath( "_attachment" );
}
@Override
protected void doHandle( final PortalWebContext context )
throws Exception
{
final HttpServletRequest request = context.getRequest();
final HttpServletResponse response = context.getResponse();
final SitePath sitePath = context.getSitePath();
final boolean downloadRequested;
final UserEntity loggedInUser;
final BlobRecord blob;
final BinaryDataEntity binaryData;
final PortalRequestTrace portalRequestTrace = PortalRequestTracer.startTracing( context.getOriginalUrl(), livePortalTraceService );
try
{
try
{
PortalRequestTracer.traceMode( portalRequestTrace, previewService );
PortalRequestTracer.traceHttpRequest( portalRequestTrace, request );
PortalRequestTracer.traceRequestedSitePath( portalRequestTrace, sitePath );
PortalRequestTracer.traceRequestedSite( portalRequestTrace, siteDao.findByKey( sitePath.getSiteKey() ) );
loggedInUser = resolveLoggedInUser( request, response, sitePath );
PortalRequestTracer.traceRequester( portalRequestTrace, loggedInUser );
final AttachmentRequestTrace attachmentRequestTrace = AttachmentRequestTracer.startTracing( livePortalTraceService );
try
{
verifyValidMenuItemInPath( sitePath );
final AttachmentRequest attachmentRequest =
attachmentRequestResolver.resolveBinaryDataKey( sitePath.getPathAndParams() );
AttachmentRequestTracer.traceAttachmentRequest( attachmentRequestTrace, attachmentRequest );
downloadRequested = resolveDownloadRequested( request );
final ContentEntity content = resolveContent( attachmentRequest, sitePath );
checkContentAccess( loggedInUser, content, downloadRequested, sitePath );
checkContentIsOnline( content, sitePath );
binaryData = resolveBinaryData( sitePath, attachmentRequest, content );
blob = binaryDataDao.getBlob( binaryData.getBinaryDataKey() );
if ( blob == null )
{
throw AttachmentNotFoundException.notFound( sitePath.getLocalPath().toString() );
}
AttachmentRequestTracer.traceSize( attachmentRequestTrace, blob.getLength() );
}
finally
{
AttachmentRequestTracer.stopTracing( attachmentRequestTrace, livePortalTraceService );
}
}
finally
{
PortalRequestTracer.stopTracing( portalRequestTrace, livePortalTraceService );
}
// serve response ...
setHttpHeaders( request, response, sitePath, loggedInUser );
putBinaryOnResponse( context, downloadRequested, binaryData, blob );
}
catch ( Exception e )
{
throw new AttachmentRequestException( context.getOriginalSitePath(), context.getReferrerHeader(), e );
}
}
private UserEntity resolveLoggedInUser( final HttpServletRequest request, final HttpServletResponse response, final SitePath sitePath )
{
UserEntity loggedInUser = securityService.getLoggedInPortalUserAsEntity();
final SiteProperties siteProperties = sitePropertiesService.getSiteProperties( sitePath.getSiteKey() );
if ( loggedInUser.isAnonymous() )
{
if ( siteProperties.getPropertyAsBoolean( SitePropertyNames.AUTOLOGIN_HTTP_REMOTE_USER_ENABLED ) )
{
loggedInUser = autoLoginService.autologinWithRemoteUser( request, siteDao.findByKey( sitePath.getSiteKey() ) );
}
}
if ( loggedInUser.isAnonymous() )
{
if ( siteProperties.getPropertyAsBoolean( SitePropertyNames.AUTOLOGIN_REMEMBER_ME_COOKIE_ENABLED ) )
{
loggedInUser = autoLoginService.autologinWithCookie( siteDao.findByKey( sitePath.getSiteKey() ), request, response );
}
}
return loggedInUser;
}
private BinaryDataEntity resolveBinaryData( final SitePath sitePath, final AttachmentRequest attachmentRequest,
final ContentEntity content )
{
final ContentVersionEntity contentVersion = resolveContentVersion( content );
final ContentBinaryDataEntity contentBinaryData = resolveContentBinaryData( contentVersion, attachmentRequest, sitePath );
return contentBinaryData.getBinaryData();
}
private void verifyValidMenuItemInPath( SitePath sitePath )
{
SiteEntity site = siteDao.findByKey( sitePath.getSiteKey() );
Path menuItemPath = getAttachmentMenuItemPath( sitePath );
MenuItemEntity menuItem = site.resolveMenuItemByPath( menuItemPath );
if ( menuItem == null )
{
throw AttachmentNotFoundException.notFound( sitePath.getLocalPath().toString() );
}
}
private Path getAttachmentMenuItemPath( SitePath sitePath )
{
String pathAsString = sitePath.getLocalPath().toString();
if ( !pathAsString.contains( ReservedLocalPaths.PATH_ATTACHMENT.toString() ) )
{
throw AttachmentNotFoundException.notFound( sitePath.getLocalPath().toString() );
}
int i = pathAsString.lastIndexOf( ReservedLocalPaths.PATH_ATTACHMENT.toString() );
String menuItemPathAsString = pathAsString.substring( 0, i );
return new Path( menuItemPathAsString );
}
private void setHttpHeaders( final HttpServletRequest request, final HttpServletResponse response, final SitePath sitePath,
final UserEntity requester )
{
final DateTime now = new DateTime();
HttpServletUtil.setDateHeader( response, now.toDate() );
final SiteProperties siteProperties = sitePropertiesService.getSiteProperties( sitePath.getSiteKey() );
final boolean cacheHeadersEnabled = siteProperties.getPropertyAsBoolean( SitePropertyNames.ATTACHMENT_CACHE_HEADERS_ENABLED );
if ( cacheHeadersEnabled )
{
final boolean forceNoCache = siteProperties.getPropertyAsBoolean( SitePropertyNames.ATTACHMENT_CACHE_HEADERS_FORCENOCACHE );
if ( forceNoCache || isInPreviewMode( request ) )
{
HttpServletUtil.setCacheControlNoCache( response );
}
else
{
Integer siteCacheSettingsMaxAge = siteProperties.getPropertyAsInteger( SitePropertyNames.ATTACHMENT_CACHE_HEADERS_MAXAGE );
boolean publicAccess = requester.isAnonymous();
enableHttpCacheHeaders( response, sitePath, now, siteCacheSettingsMaxAge, publicAccess );
}
}
}
private void putBinaryOnResponse( final PortalWebContext context, final boolean download, final BinaryDataEntity binaryData,
final BlobRecord blob )
throws IOException
{
boolean attachment = false;
final File file = blob.getAsFile();
final String mimeType = this.mimeTypeResolver.getMimeType( binaryData.getName() );
final HttpServletResponse response = context.getResponse();
if ( file != null )
{
if ( download )
{
attachment = true;
}
HttpServletRangeUtil.processRequest( context.getRequest(), response, binaryData.getName(), mimeType, file, attachment );
}
else
{
HttpServletUtil.setContentDisposition( response, download, binaryData.getName() );
response.setContentType( mimeType );
response.setContentLength( (int) blob.getLength() );
HttpServletUtil.copyNoCloseOut( blob.getStream(), response.getOutputStream() );
}
}
private boolean isInPreviewMode( final HttpServletRequest httpRequest )
{
String previewEnabled = (String) httpRequest.getAttribute( Attribute.PREVIEW_ENABLED );
return "true".equals( previewEnabled );
}
private ContentEntity resolveContent( final AttachmentRequest attachmentRequest, final SitePath sitePath )
{
final ContentEntity content = contentDao.findByKey( attachmentRequest.getContentKey() );
if ( content == null || content.isDeleted() )
{
throw AttachmentNotFoundException.notFound( sitePath.getLocalPath().toString() );
}
return content;
}
private ContentVersionEntity resolveContentVersion( final ContentEntity content )
{
return content.getMainVersion();
}
private ContentBinaryDataEntity resolveContentBinaryData( final ContentVersionEntity contentVersion,
final AttachmentRequest attachmentRequest, final SitePath sitePath )
{
final ContentBinaryDataEntity contentBinaryData = contentVersion.getContentBinaryData( attachmentRequest.getBinaryDataKey() );
if ( contentBinaryData == null )
{
throw AttachmentNotFoundException.notFound( sitePath.getLocalPath().toString() );
}
return contentBinaryData;
}
private boolean resolveDownloadRequested( final HttpServletRequest request )
{
boolean downloadRequested = "true".equals( request.getParameter( "download" ) );
downloadRequested = downloadRequested || "true".equals( request.getParameter( "_download" ) );
return downloadRequested;
}
private void checkContentIsOnline( final ContentEntity content, final SitePath sitePath )
{
if ( previewService.isInPreview() )
{
PreviewContext previewContext = previewService.getPreviewContext();
if ( previewContext.isPreviewingContent() &&
previewContext.getContentPreviewContext().treatContentAsAvailableEvenIfOffline( content.getKey() ) )
{
// when in preview, the content doesn't need to be online
return;
}
}
if ( !content.isOnline( timeService.getNowAsDateTime() ) )
{
throw AttachmentNotFoundException.notFound( sitePath.getLocalPath().toString() );
}
}
private void checkContentAccess( final UserEntity loggedInUser, final ContentEntity content, final boolean downloadRequested,
final SitePath sitePath )
{
boolean noAccessToContent = !new ContentAccessResolver( groupDao ).hasReadContentAccess( loggedInUser, content );
if ( noAccessToContent )
{
if ( loggedInUser.isAnonymous() && downloadRequested )
{
throw new PathRequiresAuthenticationException( sitePath );
}
else
{
throw AttachmentNotFoundException.noAccess( sitePath.getLocalPath().toString() );
}
}
}
@Autowired
public void setBinaryDataDao( final BinaryDataDao binaryDataDao )
{
this.binaryDataDao = binaryDataDao;
}
@Autowired
public void setMimeTypeResolver( final MimeTypeResolver mimeTypeResolver )
{
this.mimeTypeResolver = mimeTypeResolver;
}
}