|
@@ -23,11 +23,8 @@
|
|
|
package password.pwm.http.servlet.resource;
|
|
|
|
|
|
import com.github.benmanes.caffeine.cache.Cache;
|
|
|
-import org.apache.commons.io.IOUtils;
|
|
|
-import org.webjars.WebJarAssetLocator;
|
|
|
import password.pwm.PwmApplication;
|
|
|
import password.pwm.PwmConstants;
|
|
|
-import password.pwm.config.PwmSetting;
|
|
|
import password.pwm.error.ErrorInformation;
|
|
|
import password.pwm.error.PwmError;
|
|
|
import password.pwm.error.PwmUnrecoverableException;
|
|
@@ -38,10 +35,9 @@ import password.pwm.http.servlet.PwmServlet;
|
|
|
import password.pwm.svc.stats.EventRateMeter;
|
|
|
import password.pwm.svc.stats.Statistic;
|
|
|
import password.pwm.svc.stats.StatisticsManager;
|
|
|
-import password.pwm.util.java.StringUtil;
|
|
|
+import password.pwm.util.java.JavaHelper;
|
|
|
import password.pwm.util.logging.PwmLogger;
|
|
|
|
|
|
-import javax.servlet.ServletContext;
|
|
|
import javax.servlet.ServletException;
|
|
|
import javax.servlet.annotation.WebServlet;
|
|
|
import javax.servlet.http.HttpServlet;
|
|
@@ -51,21 +47,12 @@ import java.io.BufferedInputStream;
|
|
|
import java.io.BufferedOutputStream;
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
-import java.io.Closeable;
|
|
|
-import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
import java.io.OutputStream;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Arrays;
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.Collections;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
-import java.util.regex.Matcher;
|
|
|
import java.util.zip.GZIPOutputStream;
|
|
|
-import java.util.zip.ZipEntry;
|
|
|
-import java.util.zip.ZipFile;
|
|
|
|
|
|
@WebServlet(
|
|
|
name = "ResourceFileServlet",
|
|
@@ -78,6 +65,9 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass( ResourceFileServlet.class );
|
|
|
|
|
|
public static final String RESOURCE_PATH = "/public/resources";
|
|
|
+ static final String WEBJAR_BASE_URL_PATH = RESOURCE_PATH + "/webjars/";
|
|
|
+ static final String WEBJAR_BASE_FILE_PATH = "META-INF/resources/webjars";
|
|
|
+
|
|
|
public static final String THEME_CSS_PATH = "/themes/%THEME%/style.css";
|
|
|
public static final String THEME_CSS_MOBILE_PATH = "/themes/%THEME%/mobileStyle.css";
|
|
|
public static final String THEME_CSS_CONFIG_PATH = "/themes/%THEME%/configStyle.css";
|
|
@@ -85,13 +75,6 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
public static final String TOKEN_THEME = "%THEME%";
|
|
|
public static final String EMBED_THEME = "embed";
|
|
|
|
|
|
- private static final String WEBJAR_BASE_FILE_PATH = "META-INF/resources/webjars";
|
|
|
- private static final String WEBJAR_BASE_URL_PATH = RESOURCE_PATH + "/webjars/";
|
|
|
-
|
|
|
- private static final Map<String, String> WEB_JAR_VERSION_MAP = Collections.unmodifiableMap( new HashMap<>( new WebJarAssetLocator().getWebJars() ) );
|
|
|
- private static final Collection<String> WEB_JAR_ASSET_LIST = Collections.unmodifiableCollection( new ArrayList<>( new WebJarAssetLocator().getFullPathIndex().values() ) );
|
|
|
-
|
|
|
-
|
|
|
@Override
|
|
|
protected void doGet( final HttpServletRequest req, final HttpServletResponse resp )
|
|
|
throws ServletException, IOException
|
|
@@ -134,11 +117,9 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
throws IOException, PwmUnrecoverableException
|
|
|
{
|
|
|
|
|
|
- final FileResource file = resolveRequestedFile(
|
|
|
- req.getServletContext(),
|
|
|
- figureRequestPathMinusContext( req ),
|
|
|
- ResourceServletConfiguration.defaultConfiguration()
|
|
|
- );
|
|
|
+ final ResourceFileRequest resourceFileRequest = new ResourceFileRequest( null, ResourceServletConfiguration.defaultConfiguration(), req );
|
|
|
+
|
|
|
+ final FileResource file = resourceFileRequest.getRequestedFileResource();
|
|
|
|
|
|
if ( file == null || !file.exists() )
|
|
|
{
|
|
@@ -149,9 +130,8 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
handleUncachedResponse( resp, file, false );
|
|
|
}
|
|
|
|
|
|
- @SuppressWarnings( "checkstyle:MethodLength" )
|
|
|
protected void processAction( final PwmRequest pwmRequest )
|
|
|
- throws ServletException, IOException, PwmUnrecoverableException
|
|
|
+ throws IOException, PwmUnrecoverableException
|
|
|
{
|
|
|
if ( pwmRequest.getMethod() != HttpMethod.GET )
|
|
|
{
|
|
@@ -164,25 +144,13 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
final ResourceServletService resourceService = pwmApplication.getResourceServletService();
|
|
|
final ResourceServletConfiguration resourceConfiguration = resourceService.getResourceServletConfiguration();
|
|
|
|
|
|
-
|
|
|
- final String requestURI = stripNonceFromURI( resourceConfiguration, figureRequestPathMinusContext( pwmRequest.getHttpServletRequest() ) );
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- if ( handleEmbeddedURIs( pwmApplication, requestURI, pwmRequest.getPwmResponse().getHttpServletResponse(), resourceConfiguration ) )
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- catch ( Exception e )
|
|
|
- {
|
|
|
- LOGGER.error( pwmRequest, "unexpected error detecting/handling special request uri: " + e.getMessage() );
|
|
|
- }
|
|
|
+ final ResourceFileRequest resourceFileRequest = new ResourceFileRequest( pwmApplication.getConfig(), resourceConfiguration, pwmRequest.getHttpServletRequest() );
|
|
|
+ final String requestURI = resourceFileRequest.getRequestURI();
|
|
|
|
|
|
final FileResource file;
|
|
|
try
|
|
|
{
|
|
|
- file = resolveRequestedFile( this.getServletContext(), requestURI, resourceConfiguration );
|
|
|
+ file = resourceFileRequest.getRequestedFileResource();
|
|
|
}
|
|
|
catch ( PwmUnrecoverableException e )
|
|
|
{
|
|
@@ -209,47 +177,14 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
}
|
|
|
|
|
|
// Get content type by file name and set default GZIP support and content disposition.
|
|
|
- String contentType = getMimeType( file.getName() );
|
|
|
- boolean acceptsGzip = false;
|
|
|
-
|
|
|
- // If content type is unknown, then set the default value.
|
|
|
- // For all content types, see: http://www.w3schools.com/media/media_mimeref.asp
|
|
|
- // To add new content types, add new mime-mapping entry in web.xml.
|
|
|
- if ( contentType == null )
|
|
|
- {
|
|
|
- contentType = "application/octet-stream";
|
|
|
- }
|
|
|
-
|
|
|
- // If content type is text, then determine whether GZIP content encoding is supported by
|
|
|
- // the browser and expand content type with the one and right character encoding.
|
|
|
- if ( resourceConfiguration.isEnableGzip() )
|
|
|
- {
|
|
|
- if ( contentType.startsWith( "text" ) || contentType.contains( "javascript" ) )
|
|
|
- {
|
|
|
- final String acceptEncoding = pwmRequest.readHeaderValueAsString( HttpHeader.AcceptEncoding );
|
|
|
- acceptsGzip = acceptEncoding != null && accepts( acceptEncoding, "gzip" );
|
|
|
- contentType += ";charset=UTF-8";
|
|
|
- }
|
|
|
- }
|
|
|
+ final String contentType = resourceFileRequest.getReturnContentType();
|
|
|
+ final boolean acceptsGzip = resourceFileRequest.allowsCompression();
|
|
|
|
|
|
final HttpServletResponse response = pwmRequest.getPwmResponse().getHttpServletResponse();
|
|
|
- final String eTagValue = resourceConfiguration.getNonceValue();
|
|
|
|
|
|
+ if ( respondWithNotModified( pwmRequest, resourceConfiguration ) )
|
|
|
{
|
|
|
- // reply back with etag.
|
|
|
- final String ifNoneMatchValue = pwmRequest.readHeaderValueAsString( HttpHeader.If_None_Match );
|
|
|
- if ( ifNoneMatchValue != null && ifNoneMatchValue.equals( eTagValue ) )
|
|
|
- {
|
|
|
- response.reset();
|
|
|
- response.setStatus( HttpServletResponse.SC_NOT_MODIFIED );
|
|
|
- try
|
|
|
- {
|
|
|
- pwmRequest.debugHttpRequestToLog( "returning HTTP 304 status" );
|
|
|
- }
|
|
|
- catch ( PwmUnrecoverableException e2 )
|
|
|
- { /* noop */ }
|
|
|
- return;
|
|
|
- }
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
// Initialize response.
|
|
@@ -260,54 +195,22 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
try
|
|
|
{
|
|
|
boolean fromCache = false;
|
|
|
- StringBuilder debugText = new StringBuilder();
|
|
|
+ String debugText;
|
|
|
try
|
|
|
{
|
|
|
- fromCache = handleCacheableResponse( resourceConfiguration, response, file, acceptsGzip, resourceService.getCacheMap() );
|
|
|
- if ( fromCache || acceptsGzip )
|
|
|
- {
|
|
|
- debugText.append( "(" );
|
|
|
- if ( fromCache )
|
|
|
- {
|
|
|
- debugText.append( "cached" );
|
|
|
- }
|
|
|
- if ( fromCache && acceptsGzip )
|
|
|
- {
|
|
|
- debugText.append( ", " );
|
|
|
- }
|
|
|
- if ( acceptsGzip )
|
|
|
- {
|
|
|
- debugText.append( "gzip" );
|
|
|
- }
|
|
|
- debugText.append( ")" );
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- debugText = new StringBuilder( "(not cached)" );
|
|
|
- }
|
|
|
- StatisticsManager.incrementStat( pwmApplication, Statistic.HTTP_RESOURCE_REQUESTS );
|
|
|
+ fromCache = handleCacheableResponse( resourceFileRequest, response, resourceService.getCacheMap() );
|
|
|
+ debugText = makeDebugText( fromCache, acceptsGzip, false );
|
|
|
}
|
|
|
catch ( UncacheableResourceException e )
|
|
|
{
|
|
|
handleUncachedResponse( response, file, acceptsGzip );
|
|
|
- debugText = new StringBuilder();
|
|
|
- debugText.append( "(uncacheable" );
|
|
|
- if ( acceptsGzip )
|
|
|
- {
|
|
|
- debugText.append( ", gzip" );
|
|
|
- }
|
|
|
- debugText.append( ")" );
|
|
|
- }
|
|
|
- try
|
|
|
- {
|
|
|
- pwmRequest.debugHttpRequestToLog( debugText.toString() );
|
|
|
- }
|
|
|
- catch ( PwmUnrecoverableException e )
|
|
|
- {
|
|
|
- /* noop */
|
|
|
+ debugText = makeDebugText( fromCache, acceptsGzip, true );
|
|
|
}
|
|
|
|
|
|
+ pwmRequest.debugHttpRequestToLog( debugText );
|
|
|
+
|
|
|
final EventRateMeter.MovingAverage cacheHitRatio = resourceService.getCacheHitRatio();
|
|
|
+ StatisticsManager.incrementStat( pwmApplication, Statistic.HTTP_RESOURCE_REQUESTS );
|
|
|
cacheHitRatio.update( fromCache ? 1 : 0 );
|
|
|
}
|
|
|
catch ( Exception e )
|
|
@@ -316,49 +219,82 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private String makeDebugText( final boolean fromCache, final boolean acceptsGzip, final boolean uncacheable )
|
|
|
+ {
|
|
|
+ if ( uncacheable )
|
|
|
+ {
|
|
|
+ final StringBuilder debugText = new StringBuilder();
|
|
|
+ debugText.append( "(uncacheable" );
|
|
|
+ if ( acceptsGzip )
|
|
|
+ {
|
|
|
+ debugText.append( ", gzip" );
|
|
|
+ }
|
|
|
+ debugText.append( ")" );
|
|
|
+ return debugText.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( fromCache || acceptsGzip )
|
|
|
+ {
|
|
|
+ final StringBuilder debugText = new StringBuilder();
|
|
|
+ debugText.append( "(" );
|
|
|
+ if ( fromCache )
|
|
|
+ {
|
|
|
+ debugText.append( "cached" );
|
|
|
+ }
|
|
|
+ if ( fromCache && acceptsGzip )
|
|
|
+ {
|
|
|
+ debugText.append( ", " );
|
|
|
+ }
|
|
|
+ if ( acceptsGzip )
|
|
|
+ {
|
|
|
+ debugText.append( "gzip" );
|
|
|
+ }
|
|
|
+ debugText.append( ")" );
|
|
|
+ return debugText.toString();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return "(not cached)";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private boolean handleCacheableResponse(
|
|
|
- final ResourceServletConfiguration resourceServletConfiguration,
|
|
|
+ final ResourceFileRequest resourceFileRequest,
|
|
|
final HttpServletResponse response,
|
|
|
- final FileResource file,
|
|
|
- final boolean acceptsGzip,
|
|
|
final Cache<CacheKey, CacheEntry> responseCache
|
|
|
)
|
|
|
- throws UncacheableResourceException, IOException
|
|
|
+ throws UncacheableResourceException, IOException, PwmUnrecoverableException
|
|
|
{
|
|
|
+ final FileResource file = resourceFileRequest.getRequestedFileResource();
|
|
|
|
|
|
- if ( file.length() > resourceServletConfiguration.getMaxCacheBytes() )
|
|
|
+ if ( file.length() > resourceFileRequest.getResourceServletConfiguration().getMaxCacheBytes() )
|
|
|
{
|
|
|
throw new UncacheableResourceException( "file to large to cache" );
|
|
|
}
|
|
|
|
|
|
boolean fromCache = false;
|
|
|
- final CacheKey cacheKey = new CacheKey( file, acceptsGzip );
|
|
|
+ final CacheKey cacheKey = new CacheKey( file, resourceFileRequest.allowsCompression() );
|
|
|
CacheEntry cacheEntry = responseCache.getIfPresent( cacheKey );
|
|
|
if ( cacheEntry == null )
|
|
|
{
|
|
|
final Map<String, String> headers = new HashMap<>();
|
|
|
final ByteArrayOutputStream tempOutputStream = new ByteArrayOutputStream();
|
|
|
- final InputStream input = file.getInputStream();
|
|
|
|
|
|
- try
|
|
|
+ try ( InputStream input = resourceFileRequest.getRequestedFileResource().getInputStream() )
|
|
|
{
|
|
|
- if ( acceptsGzip )
|
|
|
+ if ( resourceFileRequest.allowsCompression() )
|
|
|
{
|
|
|
- final GZIPOutputStream gzipOutputStream = new GZIPOutputStream( tempOutputStream );
|
|
|
- headers.put( HttpHeader.ContentEncoding.getHttpName(), "gzip" );
|
|
|
- copy( input, gzipOutputStream );
|
|
|
- close( gzipOutputStream );
|
|
|
+ try ( GZIPOutputStream gzipOutputStream = new GZIPOutputStream( tempOutputStream ) )
|
|
|
+ {
|
|
|
+ headers.put( HttpHeader.ContentEncoding.getHttpName(), "gzip" );
|
|
|
+ JavaHelper.copy( input, gzipOutputStream );
|
|
|
+ }
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- copy( input, tempOutputStream );
|
|
|
+ JavaHelper.copy( input, tempOutputStream );
|
|
|
}
|
|
|
}
|
|
|
- finally
|
|
|
- {
|
|
|
- close( input );
|
|
|
- close( tempOutputStream );
|
|
|
- }
|
|
|
|
|
|
final byte[] entity = tempOutputStream.toByteArray();
|
|
|
headers.put( HttpHeader.ContentLength.getHttpName(), String.valueOf( entity.length ) );
|
|
@@ -375,14 +311,9 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
response.setHeader( key, cacheEntry.getHeaderStrings().get( key ) );
|
|
|
}
|
|
|
|
|
|
- final OutputStream responseOutputStream = response.getOutputStream();
|
|
|
- try
|
|
|
- {
|
|
|
- copy( new ByteArrayInputStream( cacheEntry.getEntity() ), responseOutputStream );
|
|
|
- }
|
|
|
- finally
|
|
|
+ try ( OutputStream responseOutputStream = response.getOutputStream() )
|
|
|
{
|
|
|
- close( responseOutputStream );
|
|
|
+ JavaHelper.copy( new ByteArrayInputStream( cacheEntry.getEntity() ), responseOutputStream );
|
|
|
}
|
|
|
|
|
|
return fromCache;
|
|
@@ -392,23 +323,18 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
final HttpServletResponse response,
|
|
|
final FileResource file,
|
|
|
final boolean acceptsGzip
|
|
|
- ) throws IOException
|
|
|
+ )
|
|
|
+ throws IOException
|
|
|
{
|
|
|
- // Prepare streams.
|
|
|
- OutputStream output = null;
|
|
|
- InputStream input = null;
|
|
|
-
|
|
|
- try
|
|
|
+ try (
|
|
|
+ OutputStream output = new BufferedOutputStream( response.getOutputStream() );
|
|
|
+ InputStream input = new BufferedInputStream( file.getInputStream() );
|
|
|
+ )
|
|
|
{
|
|
|
- // Open streams.
|
|
|
- input = new BufferedInputStream( file.getInputStream() );
|
|
|
- output = new BufferedOutputStream( response.getOutputStream() );
|
|
|
-
|
|
|
if ( acceptsGzip )
|
|
|
{
|
|
|
- // The browser accepts GZIP, so GZIP the content.
|
|
|
response.setHeader( HttpHeader.ContentEncoding.getHttpName(), "gzip" );
|
|
|
- output = new GZIPOutputStream( output );
|
|
|
+ JavaHelper.copy( input, new GZIPOutputStream( output ) );
|
|
|
}
|
|
|
else
|
|
|
{
|
|
@@ -418,337 +344,12 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
{
|
|
|
response.setHeader( HttpHeader.ContentLength.getHttpName(), String.valueOf( file.length() ) );
|
|
|
}
|
|
|
+ JavaHelper.copy( input, output );
|
|
|
}
|
|
|
-
|
|
|
- // Copy full range.
|
|
|
- copy( input, output );
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- // Gently close streams.
|
|
|
- close( output );
|
|
|
- close( input );
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Returns true if the given accept header accepts the given value.
|
|
|
- *
|
|
|
- * @param acceptHeader The accept header.
|
|
|
- * @param toAccept The value to be accepted.
|
|
|
- * @return True if the given accept header accepts the given value.
|
|
|
- */
|
|
|
- private static boolean accepts( final String acceptHeader, final String toAccept )
|
|
|
- {
|
|
|
- final String[] acceptValues = acceptHeader.split( "\\s*(,|;)\\s*" );
|
|
|
- Arrays.sort( acceptValues );
|
|
|
- return Arrays.binarySearch( acceptValues, toAccept ) > -1
|
|
|
- || Arrays.binarySearch( acceptValues, toAccept.replaceAll( "/.*$", "/*" ) ) > -1
|
|
|
- || Arrays.binarySearch( acceptValues, "*/*" ) > -1;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Copy the given byte range of the given input to the given output.
|
|
|
- *
|
|
|
- * @param input The input to copy the given range to the given output for.
|
|
|
- * @param output The output to copy the given range from the given input for.
|
|
|
- * @throws IOException If something fails at I/O level.
|
|
|
- */
|
|
|
- private static void copy( final InputStream input, final OutputStream output )
|
|
|
- throws IOException
|
|
|
- {
|
|
|
- IOUtils.copy( input, output );
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Close the given resource.
|
|
|
- *
|
|
|
- * @param resource The resource to be closed.
|
|
|
- */
|
|
|
- private static void close( final Closeable resource )
|
|
|
- {
|
|
|
- IOUtils.closeQuietly( resource );
|
|
|
- }
|
|
|
-
|
|
|
- static FileResource resolveRequestedFile(
|
|
|
- final ServletContext servletContext,
|
|
|
- final String resourcePathUri,
|
|
|
- final ResourceServletConfiguration resourceServletConfiguration
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- // URL-decode the file name (might contain spaces and on) and prepare file object.
|
|
|
- String filename = StringUtil.urlDecode( resourcePathUri );
|
|
|
-
|
|
|
- // parse out the session key...
|
|
|
- if ( filename.contains( ";" ) )
|
|
|
- {
|
|
|
- filename = filename.substring( 0, filename.indexOf( ";" ) );
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- if ( !filename.startsWith( RESOURCE_PATH ) )
|
|
|
- {
|
|
|
- LOGGER.warn( "illegal url request to " + filename );
|
|
|
- throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal url request" ) );
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- final FileResource resource = handleWebjarURIs( servletContext, resourcePathUri );
|
|
|
- if ( resource != null )
|
|
|
- {
|
|
|
- return resource;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- // check files system zip files.
|
|
|
- final Map<String, ZipFile> zipResources = resourceServletConfiguration.getZipResources();
|
|
|
- for ( final Map.Entry<String, ZipFile> entry : zipResources.entrySet() )
|
|
|
- {
|
|
|
- final String path = entry.getKey();
|
|
|
- if ( filename.startsWith( path ) )
|
|
|
- {
|
|
|
- final String zipSubPath = filename.substring( path.length() + 1, filename.length() );
|
|
|
- final ZipFile zipFile = entry.getValue();
|
|
|
- final ZipEntry zipEntry = zipFile.getEntry( zipSubPath );
|
|
|
- if ( zipEntry != null )
|
|
|
- {
|
|
|
- return new ZipFileResource( zipFile, zipEntry );
|
|
|
- }
|
|
|
- }
|
|
|
- if ( filename.startsWith( zipResources.get( path ).getName() ) )
|
|
|
- {
|
|
|
- LOGGER.warn( "illegal url request to " + filename + " zip resource" );
|
|
|
- throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal url request" ) );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // convert to file.
|
|
|
- final String filePath = servletContext.getRealPath( filename );
|
|
|
- final File file = new File( filePath );
|
|
|
-
|
|
|
- // figure top-most path allowed by request
|
|
|
- final String parentDirectoryPath = servletContext.getRealPath( RESOURCE_PATH );
|
|
|
- final File parentDirectory = new File( parentDirectoryPath );
|
|
|
-
|
|
|
- FileResource fileSystemResource = null;
|
|
|
- {
|
|
|
- //verify the requested page is a child of the servlet resource path.
|
|
|
- int recursions = 0;
|
|
|
- File recurseFile = file.getParentFile();
|
|
|
- while ( recurseFile != null && recursions < 100 )
|
|
|
- {
|
|
|
- if ( parentDirectory.equals( recurseFile ) )
|
|
|
- {
|
|
|
- fileSystemResource = new RealFileResource( file );
|
|
|
- break;
|
|
|
- }
|
|
|
- recurseFile = recurseFile.getParentFile();
|
|
|
- recursions++;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ( fileSystemResource == null )
|
|
|
- {
|
|
|
- LOGGER.warn( "attempt to access file outside of servlet path " + file.getAbsolutePath() );
|
|
|
- throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal file path request" ) );
|
|
|
- }
|
|
|
-
|
|
|
- if ( !fileSystemResource.exists() )
|
|
|
- {
|
|
|
- // check custom (configuration defined) zip file bundles
|
|
|
- final Map<String, FileResource> customResources = resourceServletConfiguration.getCustomFileBundle();
|
|
|
- for ( final Map.Entry<String, FileResource> entry : customResources.entrySet() )
|
|
|
- {
|
|
|
- final String customFileName = entry.getKey();
|
|
|
- final String testName = RESOURCE_PATH + "/" + customFileName;
|
|
|
- if ( testName.equals( resourcePathUri ) )
|
|
|
- {
|
|
|
- return entry.getValue();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return fileSystemResource;
|
|
|
- }
|
|
|
-
|
|
|
- private boolean handleEmbeddedURIs(
|
|
|
- final PwmApplication pwmApplication,
|
|
|
- final String requestURI,
|
|
|
- final HttpServletResponse response,
|
|
|
- final ResourceServletConfiguration resourceServletConfiguration
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException, IOException, ServletException
|
|
|
- {
|
|
|
- if ( requestURI != null )
|
|
|
- {
|
|
|
- final String embedThemeUrl = RESOURCE_PATH + THEME_CSS_PATH.replace( TOKEN_THEME, EMBED_THEME );
|
|
|
- final String embedThemeMobileUrl = RESOURCE_PATH + THEME_CSS_MOBILE_PATH.replace( TOKEN_THEME, EMBED_THEME );
|
|
|
- if ( requestURI.equalsIgnoreCase( embedThemeUrl ) )
|
|
|
- {
|
|
|
- writeConfigSettingToBody( pwmApplication, PwmSetting.DISPLAY_CSS_EMBED, response, resourceServletConfiguration );
|
|
|
- return true;
|
|
|
- }
|
|
|
- else if ( requestURI.equalsIgnoreCase( embedThemeMobileUrl ) )
|
|
|
- {
|
|
|
- writeConfigSettingToBody( pwmApplication, PwmSetting.DISPLAY_CSS_MOBILE_EMBED, response, resourceServletConfiguration );
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- private void writeConfigSettingToBody(
|
|
|
- final PwmApplication pwmApplication,
|
|
|
- final PwmSetting pwmSetting,
|
|
|
- final HttpServletResponse response,
|
|
|
- final ResourceServletConfiguration resourceServletConfiguration
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException, IOException
|
|
|
- {
|
|
|
- final String bodyText = pwmApplication.getConfig().readSettingAsString( pwmSetting );
|
|
|
- try
|
|
|
- {
|
|
|
- response.setContentType( "text/css" );
|
|
|
- addExpirationHeaders( resourceServletConfiguration, response );
|
|
|
- if ( bodyText != null && bodyText.length() > 0 )
|
|
|
- {
|
|
|
- response.setIntHeader( "Content-Length", bodyText.length() );
|
|
|
- copy( new ByteArrayInputStream( bodyText.getBytes( PwmConstants.DEFAULT_CHARSET ) ), response.getOutputStream() );
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- response.setIntHeader( "Content-Length", 0 );
|
|
|
- }
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- close( response.getOutputStream() );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private String stripNonceFromURI(
|
|
|
- final ResourceServletConfiguration resourceServletConfiguration,
|
|
|
- final String uriString
|
|
|
- )
|
|
|
- {
|
|
|
- if ( !resourceServletConfiguration.isEnablePathNonce() )
|
|
|
- {
|
|
|
- return uriString;
|
|
|
- }
|
|
|
-
|
|
|
- final Matcher theMatcher = resourceServletConfiguration.getNoncePattern().matcher( uriString );
|
|
|
-
|
|
|
- if ( theMatcher.find() )
|
|
|
- {
|
|
|
- return theMatcher.replaceFirst( "" );
|
|
|
- }
|
|
|
-
|
|
|
- return uriString;
|
|
|
- }
|
|
|
-
|
|
|
- private String figureRequestPathMinusContext( final HttpServletRequest httpServletRequest )
|
|
|
- {
|
|
|
- final String requestURI = httpServletRequest.getRequestURI();
|
|
|
- return requestURI.substring( httpServletRequest.getContextPath().length(), requestURI.length() );
|
|
|
- }
|
|
|
-
|
|
|
- private static FileResource handleWebjarURIs(
|
|
|
- final ServletContext servletContext,
|
|
|
- final String resourcePathUri
|
|
|
- )
|
|
|
- throws PwmUnrecoverableException
|
|
|
- {
|
|
|
- if ( resourcePathUri.startsWith( WEBJAR_BASE_URL_PATH ) )
|
|
|
- {
|
|
|
- // This allows us to override a webjar file, if needed. Mostly helpful during development.
|
|
|
- final File file = new File( servletContext.getRealPath( resourcePathUri ) );
|
|
|
- if ( file.exists() )
|
|
|
- {
|
|
|
- return new RealFileResource( file );
|
|
|
- }
|
|
|
-
|
|
|
- final String remainingPath = resourcePathUri.substring( WEBJAR_BASE_URL_PATH.length(), resourcePathUri.length() );
|
|
|
-
|
|
|
- final String webJarName;
|
|
|
- final String webJarPath;
|
|
|
- {
|
|
|
- final int slashIndex = remainingPath.indexOf( "/" );
|
|
|
- if ( slashIndex < 0 )
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
- webJarName = remainingPath.substring( 0, slashIndex );
|
|
|
- webJarPath = remainingPath.substring( slashIndex + 1, remainingPath.length() );
|
|
|
- }
|
|
|
-
|
|
|
- final String versionString = WEB_JAR_VERSION_MAP.get( webJarName );
|
|
|
- if ( versionString == null )
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- final String fullPath = WEBJAR_BASE_FILE_PATH + "/" + webJarName + "/" + versionString + "/" + webJarPath;
|
|
|
- if ( WEB_JAR_ASSET_LIST.contains( fullPath ) )
|
|
|
- {
|
|
|
- final ClassLoader classLoader = servletContext.getClassLoader();
|
|
|
- final InputStream inputStream = classLoader.getResourceAsStream( fullPath );
|
|
|
-
|
|
|
- if ( inputStream != null )
|
|
|
- {
|
|
|
- return new InputStreamFileResource( inputStream, fullPath );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- private static class InputStreamFileResource implements FileResource
|
|
|
- {
|
|
|
- private final InputStream inputStream;
|
|
|
- private final String fullPath;
|
|
|
-
|
|
|
- InputStreamFileResource( final InputStream inputStream, final String fullPath )
|
|
|
- {
|
|
|
- this.inputStream = inputStream;
|
|
|
- this.fullPath = fullPath;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public InputStream getInputStream( ) throws IOException
|
|
|
- {
|
|
|
- return inputStream;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public long length( )
|
|
|
- {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public long lastModified( )
|
|
|
- {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean exists( )
|
|
|
- {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public String getName( )
|
|
|
- {
|
|
|
- return fullPath;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
private void addExpirationHeaders( final ResourceServletConfiguration resourceServletConfiguration, final HttpServletResponse httpResponse )
|
|
|
{
|
|
|
httpResponse.setDateHeader( "Expires", System.currentTimeMillis() + ( resourceServletConfiguration.getCacheExpireSeconds() * 1000 ) );
|
|
@@ -756,16 +357,26 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
|
|
|
httpResponse.setHeader( "Vary", "Accept-Encoding" );
|
|
|
}
|
|
|
|
|
|
- private String getMimeType( final String filename )
|
|
|
+ private boolean respondWithNotModified( final PwmRequest pwmRequest, final ResourceServletConfiguration resourceConfiguration )
|
|
|
{
|
|
|
- final String contentType = getServletContext().getMimeType( filename );
|
|
|
- if ( contentType == null )
|
|
|
+ final String eTagValue = resourceConfiguration.getNonceValue();
|
|
|
+ final HttpServletResponse response = pwmRequest.getPwmResponse().getHttpServletResponse();
|
|
|
+
|
|
|
+ final String ifNoneMatchValue = pwmRequest.readHeaderValueAsString( HttpHeader.If_None_Match );
|
|
|
+ if ( ifNoneMatchValue != null && ifNoneMatchValue.equals( eTagValue ) )
|
|
|
{
|
|
|
- if ( filename.endsWith( ".woff2" ) )
|
|
|
+ // reply back with etag.
|
|
|
+ response.reset();
|
|
|
+ response.setStatus( HttpServletResponse.SC_NOT_MODIFIED );
|
|
|
+ try
|
|
|
{
|
|
|
- return "font/woff2";
|
|
|
+ pwmRequest.debugHttpRequestToLog( "returning HTTP 304 status" );
|
|
|
}
|
|
|
+ catch ( PwmUnrecoverableException e2 )
|
|
|
+ { /* noop */ }
|
|
|
+ return true;
|
|
|
}
|
|
|
- return contentType;
|
|
|
+
|
|
|
+ return false;
|
|
|
}
|
|
|
}
|