/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.framework.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.google.common.io.ByteStreams;
public class HttpServletUtil
{
private final static Logger LOG = LoggerFactory.getLogger( HttpServletUtil.class );
private static final int BUFFER_SIZE = 4096;
private static final String SDF_EXPIRES_PATTERN = "E, dd MMM yyyy HH:mm:ss z";
private static final String SDF_DATE_PATTERN = "E, dd MMM yyyy HH:mm:ss z";
private static final TimeZone TIMEZONE_GMT = TimeZone.getTimeZone( "GMT" );
public static void setDateHeader( HttpServletResponse response, Date dateTime )
{
final SimpleDateFormat dateFormat = new SimpleDateFormat( SDF_DATE_PATTERN, Locale.ENGLISH );
dateFormat.setTimeZone( TIMEZONE_GMT );
response.setHeader( "Date", dateFormat.format( dateTime ) );
}
public static void setExpiresHeader( HttpServletResponse response, Date expirationTime )
{
final SimpleDateFormat dateFormat = new SimpleDateFormat( SDF_EXPIRES_PATTERN, Locale.ENGLISH );
dateFormat.setTimeZone( TIMEZONE_GMT );
response.setHeader( "Expires", dateFormat.format( expirationTime ) );
}
public static void setCacheControl( HttpServletResponse response, HttpCacheControlSettings settings )
{
StringBuilder s = new StringBuilder();
if ( settings.publicAccess )
{
s.append( "public" ); //Indicates that the response may be cached by any cache
}
else
{
s.append(
"private" ); //Indicates that all or part of the response message is intended for a single user and must not be cached by a shared cache.
}
if ( settings.maxAgeSecondsToLive != null )
{
s.append( ", max-age=" ).append( settings.maxAgeSecondsToLive );
}
response.setHeader( "Cache-Control", s.toString() );
}
public static void setCacheControlNoCache( HttpServletResponse response )
{
response.setHeader( "Cache-Control", "private, no-cache, no-store" ); //HTTP 1.1
response.setHeader( "Pragma", "no-cache" ); //HTTP 1.0
response.setDateHeader( "Expires", -1 ); //prevents caching at the proxy server
}
public static void setContentDisposition( HttpServletResponse response, boolean attachment, String filename )
{
StringBuilder value = new StringBuilder();
if ( attachment )
{
value.append( "attachment" );
}
else
{
value.append( "inline" );
}
if ( ( filename != null ) && filename.length() > 0 )
{
value.append( ";filename=\"" ).append( filename ).append( "\"" );
}
response.setHeader( "Content-Disposition", value.toString() );
}
public static String resolveMimeType( ServletContext servletContext, String filename )
{
final WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext( servletContext );
final MimeTypeResolver mimeTypeResolver = (MimeTypeResolver) wac.getBean( "mimeTypeResolver" );
return mimeTypeResolver.getMimeType( filename );
}
/**
* Copy the contents of the given InputStream to the given OutputStream.
*
* @param from The InputStream to copy from.
* @param to The OutputStream to copy to.
* @return The number of bytes copied from the input stream to the output stream.
* @throws IOException If an I/O error occurs.
*/
public static long copyNoCloseOut( final InputStream from, final OutputStream to )
throws IOException
{
long byteCount = 0;
try
{
try
{
byteCount = ByteStreams.copy( from, to );
to.flush();
return byteCount;
}
catch ( SocketException e )
{
LOG.info( "Error writing to OutputStream: " + e.getMessage() );
}
catch ( IOException e )
{
LOG.warn( "Error writing to OutputStream: " + e.getMessage() );
}
}
finally
{
try
{
from.close();
}
catch ( IOException e )
{
LOG.warn( "Error closing InputStream: " + e.getMessage() );
}
}
return byteCount;
}
public static int copyNoCloseOut( Reader in, Writer out )
throws IOException
{
int byteCount = 0;
char[] buffer = new char[BUFFER_SIZE];
int bytesRead;
while ( ( bytesRead = in.read( buffer ) ) != -1 )
{
try
{
out.write( buffer, 0, bytesRead );
}
catch ( IOException e )
{
LOG.warn( "Error writing to outputstream: " + e.getMessage() +
". Closing inputstream and passing on the original exception" );
try
{
in.close();
}
catch ( IOException ex )
{
LOG.warn( "Error closing inputstream: " + ex.getMessage() );
}
throw e;
}
byteCount += bytesRead;
}
out.flush();
in.close();
return byteCount;
}
public static boolean isContentModifiedAccordingToIfNoneMatchHeader( HttpServletRequest req, String etagFromContent )
{
String etagFromHeader = req.getHeader( "If-None-Match" );
return !etagFromContent.equals( etagFromHeader );
}
public static void setEtag( HttpServletResponse res, String etag )
{
res.setHeader( "ETag", etag );
}
public static boolean checkHeaderContainsETag( final String header, final String eTag )
{
String[] matchValues = header.split( "\\s*,\\s*" );
Arrays.sort( matchValues );
return Arrays.binarySearch( matchValues, eTag ) > -1 || Arrays.binarySearch( matchValues, "*" ) > -1;
}
/**
* Check header with given value
*
* @param header accept header.
* @param value value to be accepted.
* @return <code>TRUE</code> if header apply the given value, <code>FALSE</code> otherwise
*/
public static boolean checkHeaderContainsValue( final String header, final String value )
{
String[] values = header.split( "\\s*(,|;)\\s*" );
Arrays.sort( values );
return Arrays.binarySearch( values, value ) > -1 ||
Arrays.binarySearch( values, value.replaceAll( "/.*$", "/*" ) ) > -1 ||
Arrays.binarySearch( values, "*/*" ) > -1;
}
/**
* Find the right scheme for the original request, independent of where in the network, Enonic CMS is running.
* The method is checking the "Forwarded" header first, then the "X-Forwarded-Proto" header. If none of these are set, then the
* standard call to <code>request.getScheme()</code> is used.
*
* @param request The HttpRequest.
* @return The original scheme used for the request from the browser.
*/
public static String getScheme( final HttpServletRequest request )
{
final String originalScheme = request.getHeader( "Forwarded" );
if ( originalScheme != null && !originalScheme.equals( "" ) )
{
String[] pairs = originalScheme.split( ";" );
for ( String pair : pairs )
{
if ( pair.startsWith( "proto" ) )
{
return pair.substring( 6 );
}
}
}
else
{
final String originalXScheme = request.getHeader( "X-Forwarded-Proto" );
if ( originalXScheme != null && !originalXScheme.equals( "" ) )
{
return originalXScheme;
}
}
return request.getScheme();
}
}