--- ./ResponseIncludeWrapper.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/ResponseIncludeWrapper.java Fri Jan 14 10:34:00 2005 @@ -13,6 +13,10 @@ import java.io.IOException; import java.io.PrintWriter; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; + import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; @@ -24,12 +28,17 @@ * @version $Revision: 1.5 $, $Date: 2004/09/01 18:33:33 $ */ public class ResponseIncludeWrapper extends HttpServletResponseWrapper { + private static final String CONTENT_TYPE = "content-type"; + private static final String LAST_MODIFIED = "last-modified"; + private DateFormat lmParser = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z"); /** * Our ServletOutputStream */ protected ServletOutputStream originalServletOutputStream; protected ServletOutputStream servletOutputStream; protected PrintWriter printWriter; + protected long lastModified = 0; + private String contentType = null; /** @@ -98,5 +107,85 @@ return servletOutputStream; } throw new IllegalStateException(); + } + + + /** + * Returns the value of the last-modified header field. The result is + * the number of milliseconds since January 1, 1970 GMT. + * + * @return the date the resource referenced by this ResponseIncludeWrapper was last modified, or 0 if not known. + */ + public long getLastModified() { + return lastModified; + } + + + /** + * Returns the value of the content-type header field. + * + * @return the content type of the resource referenced by this ResponseIncludeWrapper, or null if not known. + */ + public String getContentType() + { + return contentType; + } + + + public void addDateHeader(String name, long value) { + super.addDateHeader(name, value); + String lname = name.toLowerCase(); + if (lname.equals(LAST_MODIFIED)) { + lastModified = value; + } + } + + + public void addHeader(String name, String value) { + super.addHeader(name, value); + String lname = name.toLowerCase(); + if (lname.equals(LAST_MODIFIED)) { + try { + lastModified = lmParser.parse(value).getTime(); + } catch (ParseException e) { + ; + } + } + else if (lname.equals(CONTENT_TYPE)) + { + contentType = value; + } + } + + + public void setDateHeader(String name, long value) { + super.setDateHeader(name, value); + String lname = name.toLowerCase(); + if (lname.equals(LAST_MODIFIED)) { + lastModified = value; + } + } + + + public void setHeader(String name, String value) { + super.setHeader(name, value); + String lname = name.toLowerCase(); + if (lname.equals(LAST_MODIFIED)) { + try { + lastModified = lmParser.parse(value).getTime(); + } catch (ParseException e) { + ; + } + } + else if (lname.equals(CONTENT_TYPE)) + { + contentType = value; + } + } + + public void setContentType(String value) + { + super.setContentType(value); + contentType = value; } } --- ./SSICommand.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSICommand.java Fri Jan 14 11:37:00 2005 @@ -34,10 +34,11 @@ * The parameter values * @param writer * the writer to output to + * @return the most current modified date resulting from any SSI commands * @throws SSIStopProcessingException * if SSI processing should be aborted */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) throws SSIStopProcessingException; } --- ./SSIConditional.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIConditional.java Fri Jan 14 11:47:00 2005 @@ -23,9 +23,10 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) throws SSIStopProcessingException { + long lastModified = System.currentTimeMillis(); // Retrieve the current state information SSIConditionalState state = ssiMediator.getConditionalState(); if ("if".equalsIgnoreCase(commandName)) { @@ -33,7 +34,7 @@ // except count it if (state.processConditionalCommandsOnly) { state.nestingCount++; - return; + return lastModified; } state.nestingCount = 0; // Evaluate the expression @@ -48,12 +49,12 @@ } else if ("elif".equalsIgnoreCase(commandName)) { // No need to even execute if we are nested in // a false branch - if (state.nestingCount > 0) return; + if (state.nestingCount > 0) return lastModified; // If a branch was already taken in this if block // then disable output and return if (state.branchTaken) { state.processConditionalCommandsOnly = true; - return; + return lastModified; } // Evaluate the expression if (evaluateArguments(paramNames, paramValues, ssiMediator)) { @@ -68,7 +69,7 @@ } else if ("else".equalsIgnoreCase(commandName)) { // No need to even execute if we are nested in // a false branch - if (state.nestingCount > 0) return; + if (state.nestingCount > 0) return lastModified; // If we've already taken another branch then // disable output otherwise enable it. state.processConditionalCommandsOnly = state.branchTaken; @@ -80,7 +81,7 @@ // one level on the nesting count if (state.nestingCount > 0) { state.nestingCount--; - return; + return lastModified; } // Turn output back on state.processConditionalCommandsOnly = false; @@ -93,6 +94,7 @@ //throw new SsiCommandException( "Not a conditional command:" + // cmdName ); } + return lastModified; } --- ./SSIConfig.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIConfig.java Fri Jan 14 11:48:00 2005 @@ -24,8 +24,9 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { + long lastModified = 0; for (int i = 0; i < paramNames.length; i++) { String paramName = paramNames[i]; String paramValue = paramValues[i]; @@ -33,10 +34,13 @@ .substituteVariables(paramValue); if (paramName.equalsIgnoreCase("errmsg")) { ssiMediator.setConfigErrMsg(substitutedValue); + lastModified = System.currentTimeMillis(); } else if (paramName.equalsIgnoreCase("sizefmt")) { ssiMediator.setConfigSizeFmt(substitutedValue); + lastModified = System.currentTimeMillis(); } else if (paramName.equalsIgnoreCase("timefmt")) { ssiMediator.setConfigTimeFmt(substitutedValue); + lastModified = System.currentTimeMillis(); } else { ssiMediator.log("#config--Invalid attribute: " + paramName); //We need to fetch this value each time, since it may change @@ -46,5 +50,6 @@ writer.write(configErrMsg); } } + return lastModified; } } --- ./SSIEcho.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIEcho.java Fri Jan 14 11:49:00 2005 @@ -28,8 +28,9 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { + long lastModified = 0; String encoding = DEFAULT_ENCODING; String errorMessage = ssiMediator.getConfigErrMsg(); for (int i = 0; i < paramNames.length; i++) { @@ -42,6 +43,7 @@ variableValue = MISSING_VARIABLE_VALUE; } writer.write(variableValue); + lastModified = System.currentTimeMillis(); } else if (paramName.equalsIgnoreCase("encoding")) { if (isValidEncoding(paramValue)) { encoding = paramValue; @@ -54,6 +56,7 @@ writer.write(errorMessage); } } + return lastModified; } --- ./SSIExec.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIExec.java Fri Jan 14 11:51:00 2005 @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; + import org.apache.catalina.util.IOTools; /** * Implements the Server-side #exec command @@ -33,16 +34,17 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { + long lastModified = 0; String configErrMsg = ssiMediator.getConfigErrMsg(); String paramName = paramNames[0]; String paramValue = paramValues[0]; String substitutedValue = ssiMediator.substituteVariables(paramValue); if (paramName.equalsIgnoreCase("cgi")) { - ssiInclude.process(ssiMediator, "include", - new String[]{"virtual"}, new String[]{substitutedValue}, - writer); + lastModified = ssiInclude.process(ssiMediator, "include", + new String[]{"virtual"}, new String[]{substitutedValue}, + writer); } else if (paramName.equalsIgnoreCase("cmd")) { boolean foundProgram = false; try { @@ -57,6 +59,7 @@ IOTools.flow(stdErrReader, writer, buf); IOTools.flow(stdOutReader, writer, buf); proc.waitFor(); + lastModified = System.currentTimeMillis(); } catch (InterruptedException e) { ssiMediator.log("Couldn't exec file: " + substitutedValue, e); writer.write(configErrMsg); @@ -68,5 +71,6 @@ ssiMediator.log("Couldn't exec file: " + substitutedValue, e); } } + return lastModified; } } --- ./SSIFlastmod.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIFlastmod.java Fri Jan 14 11:43:00 2005 @@ -28,8 +28,9 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { + long lastModified = 0; String configErrMsg = ssiMediator.getConfigErrMsg(); StringBuffer buf = new StringBuffer(); for (int i = 0; i < paramNames.length; i++) { @@ -41,7 +42,7 @@ if (paramName.equalsIgnoreCase("file") || paramName.equalsIgnoreCase("virtual")) { boolean virtual = paramName.equalsIgnoreCase("virtual"); - long lastModified = ssiMediator.getFileLastModified( + lastModified = ssiMediator.getFileLastModified( substitutedValue, virtual); Date date = new Date(lastModified); String configTimeFmt = ssiMediator.getConfigTimeFmt(); @@ -58,6 +59,7 @@ writer.write(configErrMsg); } } + return lastModified; } --- ./SSIFsize.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIFsize.java Fri Jan 14 11:44:00 2005 @@ -30,8 +30,9 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { + long lastModified = 0; String configErrMsg = ssiMediator.getConfigErrMsg(); for (int i = 0; i < paramNames.length; i++) { String paramName = paramNames[i]; @@ -42,6 +43,8 @@ if (paramName.equalsIgnoreCase("file") || paramName.equalsIgnoreCase("virtual")) { boolean virtual = paramName.equalsIgnoreCase("virtual"); + lastModified = ssiMediator.getFileLastModified( + substitutedValue, virtual); long size = ssiMediator.getFileSize(substitutedValue, virtual); String configSizeFmt = ssiMediator.getConfigSizeFmt(); @@ -56,6 +59,7 @@ writer.write(configErrMsg); } } + return lastModified; } --- ./SSIInclude.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIInclude.java Fri Jan 14 11:53:00 2005 @@ -25,8 +25,9 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { + long lastModified = 0; String configErrMsg = ssiMediator.getConfigErrMsg(); for (int i = 0; i < paramNames.length; i++) { String paramName = paramNames[i]; @@ -37,6 +38,8 @@ if (paramName.equalsIgnoreCase("file") || paramName.equalsIgnoreCase("virtual")) { boolean virtual = paramName.equalsIgnoreCase("virtual"); + lastModified = ssiMediator.getFileLastModified( + substitutedValue, virtual); String text = ssiMediator.getFileText(substitutedValue, virtual); writer.write(text); @@ -51,5 +54,6 @@ writer.write(configErrMsg); } } + return lastModified; } } --- ./SSIMediator.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIMediator.java Fri Jan 14 11:26:00 2005 @@ -41,7 +41,7 @@ protected String configSizeFmt = DEFAULT_CONFIG_SIZE_FMT; protected String className = getClass().getName(); protected SSIExternalResolver ssiExternalResolver; - protected Date lastModifiedDate; + protected long lastModifiedDate; protected int debug; protected Strftime strftime; protected SSIConditionalState conditionalState = new SSIConditionalState(); @@ -64,7 +64,7 @@ public SSIMediator(SSIExternalResolver ssiExternalResolver, - Date lastModifiedDate, int debug) { + long lastModifiedDate, int debug) { this.ssiExternalResolver = ssiExternalResolver; this.lastModifiedDate = lastModifiedDate; this.debug = debug; @@ -315,7 +315,7 @@ setVariableValue("DATE_LOCAL", null); ssiExternalResolver.setVariableValue(className + ".DATE_LOCAL", retVal); - retVal = formatDate(lastModifiedDate, null); + retVal = formatDate(new Date(lastModifiedDate), null); setVariableValue("LAST_MODIFIED", null); ssiExternalResolver.setVariableValue(className + ".LAST_MODIFIED", retVal); --- ./SSIPrintenv.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIPrintenv.java Fri Jan 14 11:46:00 2005 @@ -24,8 +24,9 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) { + long lastModified = 0; //any arguments should produce an error if (paramNames.length > 0) { String errorMessage = ssiMediator.getConfigErrMsg(); @@ -46,7 +47,9 @@ writer.write('='); writer.write(variableValue); writer.write('\n'); + lastModified = System.currentTimeMillis(); } } + return lastModified; } } --- ./SSIProcessor.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIProcessor.java Fri Jan 14 11:37:00 2005 @@ -15,9 +15,9 @@ import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; -import java.util.Date; import java.util.HashMap; import java.util.StringTokenizer; + import org.apache.catalina.util.IOTools; /** * The entry point to SSI processing. This class does the actual parsing, @@ -36,7 +36,16 @@ protected SSIExternalResolver ssiExternalResolver; protected HashMap commands = new HashMap(); protected int debug; - + + /** + * The servlet context attribute under which we store a flag used + * to mark this request as having been processed by the SSIServlet. + * We do this because of the pathInfo mangling happening when using + * the CGIServlet in conjunction with the SSI servlet. (value stored + * as an object of type String) + */ + public static final String SSI_FLAG_ATTR = + "org.apache.catalina.ssi.SSIServlet"; public SSIProcessor(SSIExternalResolver ssiExternalResolver, int debug) { this.ssiExternalResolver = ssiExternalResolver; @@ -76,11 +85,12 @@ * the reader to read the file containing SSIs from * @param writer * the writer to write the file with the SSIs processed. + * @return the most current modified date resulting from any SSI commands * @throws IOException * when things go horribly awry. Should be unlikely since the * SSICommand usually catches 'normal' IOExceptions. */ - public void process(Reader reader, Date lastModifiedDate, + public long process(Reader reader, long lastModifiedDate, PrintWriter writer) throws IOException { SSIMediator ssiMediator = new SSIMediator(ssiExternalResolver, lastModifiedDate, debug); @@ -142,8 +152,11 @@ // command is not conditional if (!ssiMediator.getConditionalState().processConditionalCommandsOnly || ssiCommand instanceof SSIConditional) { - ssiCommand.process(ssiMediator, strCmd, - paramNames, paramValues, writer); + long lmd = ssiCommand.process(ssiMediator, strCmd, + paramNames, paramValues, writer); + if (lmd > lastModifiedDate) { + lastModifiedDate = lmd; + } } } if (errorMessage != null) { @@ -160,6 +173,7 @@ //If we are here, then we have already stopped processing, so all // is good } + return lastModifiedDate; } --- ./SSIServlet.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIServlet.java Fri Jan 14 11:34:00 2005 @@ -19,13 +19,12 @@ import java.io.StringWriter; import java.net.URL; import java.net.URLConnection; -import java.util.Date; + import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.catalina.Globals; /** * Servlet to process SSI requests within a webpage. Mapped to a path from * within web.xml. @@ -166,7 +165,7 @@ res.setDateHeader("Expires", (new java.util.Date()).getTime() + expires.longValue() * 1000); } - req.setAttribute(Globals.SSI_FLAG_ATTR, "true"); + req.setAttribute(SSIProcessor.SSI_FLAG_ATTR, "true"); processSSI(req, res, resource); } @@ -174,7 +173,7 @@ protected void processSSI(HttpServletRequest req, HttpServletResponse res, URL resource) throws IOException { SSIExternalResolver ssiExternalResolver = new SSIServletExternalResolver( - this, req, res, isVirtualWebappRelative, debug); + getServletContext(), req, res, isVirtualWebappRelative, debug); SSIProcessor ssiProcessor = new SSIProcessor(ssiExternalResolver, debug); PrintWriter printWriter = null; @@ -189,8 +188,10 @@ InputStream resourceInputStream = resourceInfo.getInputStream(); BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(resourceInputStream)); - Date lastModifiedDate = new Date(resourceInfo.getLastModified()); - ssiProcessor.process(bufferedReader, lastModifiedDate, printWriter); + long lastModified = ssiProcessor.process(bufferedReader, resourceInfo.getLastModified(), printWriter); + if (lastModified > 0) { + res.setDateHeader("Last-Modified", lastModified); + } if (buffered) { printWriter.flush(); String text = stringWriter.toString(); --- ./SSIServletExternalResolver.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSIServletExternalResolver.java Fri Jan 14 09:31:00 2005 @@ -12,16 +12,17 @@ import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.util.Collection; import java.util.Date; import java.util.Enumeration; + import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** @@ -37,17 +38,17 @@ "QUERY_STRING", "QUERY_STRING_UNESCAPED", "REMOTE_ADDR", "REMOTE_HOST", "REMOTE_USER", "REQUEST_METHOD", "SCRIPT_NAME", "SERVER_NAME", "SERVER_PORT", "SERVER_PROTOCOL", "SERVER_SOFTWARE"}; - protected HttpServlet servlet; + protected ServletContext context; protected HttpServletRequest req; protected HttpServletResponse res; protected boolean isVirtualWebappRelative; protected int debug; - public SSIServletExternalResolver(HttpServlet servlet, + public SSIServletExternalResolver(ServletContext context, HttpServletRequest req, HttpServletResponse res, boolean isVirtualWebappRelative, int debug) { - this.servlet = servlet; + this.context = context; this.req = req; this.res = res; this.isVirtualWebappRelative = isVirtualWebappRelative; @@ -60,9 +61,9 @@ //is the same as Servlet.log( message ), since API //doesn't seem to say so. if (throwable != null) { - servlet.log(message, throwable); + context.log(message, throwable); } else { - servlet.log(message); + context.log(message); } } @@ -160,7 +161,14 @@ } else if (name.equalsIgnoreCase("QUERY_STRING_UNESCAPED")) { String queryString = req.getQueryString(); if (queryString != null) { - retVal = URLDecoder.decode(queryString); + try + { + retVal = URLDecoder.decode(queryString, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + retVal = queryString; + } } } else if (name.equalsIgnoreCase("REMOTE_ADDR")) { retVal = req.getRemoteAddr(); @@ -179,8 +187,7 @@ } else if (name.equalsIgnoreCase("SERVER_PROTOCOL")) { retVal = req.getProtocol(); } else if (name.equalsIgnoreCase("SERVER_SOFTWARE")) { - ServletContext servletContext = servlet.getServletContext(); - retVal = servletContext.getServerInfo(); + retVal = context.getServerInfo(); } return retVal; } @@ -250,26 +257,23 @@ + nonVirtualPath); } String path = getAbsolutePath(nonVirtualPath); - ServletContext servletContext = servlet.getServletContext(); ServletContextAndPath csAndP = new ServletContextAndPath( - servletContext, path); + context, path); return csAndP; } protected ServletContextAndPath getServletContextAndPathFromVirtualPath( String virtualPath) throws IOException { - ServletContext servletContext = servlet.getServletContext(); - String path = null; if (!virtualPath.startsWith("/") && !virtualPath.startsWith("\\")) { - path = getAbsolutePath(virtualPath); + return new ServletContextAndPath(context, getAbsolutePath(virtualPath)); } else { String normalized = SSIServletRequestUtil.normalize(virtualPath); if (isVirtualWebappRelative) { - path = normalized; + return new ServletContextAndPath(context, normalized); } else { - servletContext = servletContext.getContext(normalized); - if (servletContext == null) { + ServletContext normContext = context.getContext(normalized); + if (normContext == null) { throw new IOException("Couldn't get context for path: " + normalized); } @@ -277,19 +281,19 @@ // to remove, // ie: // '/file1.shtml' vs '/appName1/file1.shtml' - if (!isRootContext(servletContext)) { - path = getPathWithoutContext(normalized); - if (path == null) { + if (!isRootContext(normContext)) { + String noContext = getPathWithoutContext(normalized); + if (noContext == null) { throw new IOException( "Couldn't remove context from path: " + normalized); } + return new ServletContextAndPath(normContext, noContext); } else { - path = normalized; + return new ServletContextAndPath(normContext, normalized); } } } - return new ServletContextAndPath(servletContext, path); } --- ./SSISet.java Fri Oct 29 16:13:00 2004 +++ ../ssi_new/SSISet.java Fri Jan 14 11:52:00 2005 @@ -23,9 +23,10 @@ /** * @see SSICommand */ - public void process(SSIMediator ssiMediator, String commandName, + public long process(SSIMediator ssiMediator, String commandName, String[] paramNames, String[] paramValues, PrintWriter writer) throws SSIStopProcessingException { + long lastModified = 0; String errorMessage = ssiMediator.getConfigErrMsg(); String variableName = null; for (int i = 0; i < paramNames.length; i++) { @@ -39,6 +40,7 @@ .substituteVariables(paramValue); ssiMediator.setVariableValue(variableName, substitutedValue); + lastModified = System.currentTimeMillis(); } else { ssiMediator.log("#set--no variable specified"); writer.write(errorMessage); @@ -50,5 +52,6 @@ throw new SSIStopProcessingException(); } } + return lastModified; } } --- SSIFilter.java Fri Jan 14 13:25:44 2005 +++ ../ssi_new/SSIFilter.java Fri Jan 14 13:30:06 2005 @@ -0,0 +1,189 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. Licensed under the + * Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License + * at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable + * law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.apache.catalina.ssi; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +/** + * Filter to process SSI requests within a webpage. Mapped to a content types from + * within web.xml. + * + * Based on code from org.apache.catalina.ssi.SSIServlet. + * + * @author David Becker + * @version $Revision: 1.0 $, $Date: 2005/01/14 13:28:00 $ + * @see org.apache.catalina.ssi.SSIServlet + */ +public class SSIFilter implements Filter { + /** Configuration for this filter. */ + protected FilterConfig config = null; + /** Debug level for this filter. */ + protected int debug = 0; + /** Expiration time in seconds for the doc. */ + protected Long expires = null; + /** virtual path can be webapp-relative */ + protected boolean isVirtualWebappRelative = false; + /** content types allowed for SSI processing */ + protected List contentTypes = new ArrayList(); + /** should all content types be allowed */ + protected boolean allowAllContentTypes = false; + + + //----------------- Public methods. + /** + * Initialize this filter. + * + * @exception ServletException + * if an error occurs + */ + public void init(FilterConfig config) throws ServletException { + this.config = config; + String value = null; + try { + value = config.getInitParameter("debug"); + debug = Integer.parseInt(value); + } catch (Throwable t) { + ; + } + try { + value = config.getInitParameter( + "isVirtualWebappRelative"); + isVirtualWebappRelative = Integer.parseInt(value) > 0?true:false; + } catch (Throwable t) { + ; + } + try { + value = config.getInitParameter("expires"); + expires = Long.valueOf(value); + } catch (NumberFormatException e) { + expires = null; + config.getServletContext().log("Invalid format for expires initParam; expected integer (seconds)"); + } catch (Throwable t) { + ; + } + try { + String types = config.getInitParameter("contentTypes"); + allowAllContentTypes = (types.equals("*") || types.equals("*/*")); + if ((types != null) && !types.equals("")) + { + contentTypes = Arrays.asList(types.split(",")); + } + else + { + contentTypes.add("text/html"); + config.getServletContext().log("No contentTypes initParam provided; defaulting to text/html"); + } + } catch (Throwable t) { + ; + } + if (debug > 0) + config.getServletContext().log("SSIFilter.init() SSI invoker started with 'debug'=" + debug); + } + + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + // cast once + HttpServletRequest req = (HttpServletRequest)request; + HttpServletResponse res = (HttpServletResponse)response; + + // indicate that we're in SSI processing + req.setAttribute(SSIProcessor.SSI_FLAG_ATTR, "true"); + + // setup to capture output + ByteArrayServletOutputStream basos = new ByteArrayServletOutputStream(); + ResponseIncludeWrapper responseIncludeWrapper = new ResponseIncludeWrapper(res, basos); + + // process remainder of filter chain + chain.doFilter(req, responseIncludeWrapper); + + // we can't assume the chain flushed its output + responseIncludeWrapper.flushOutputStreamOrWriter(); + byte[] bytes = basos.toByteArray(); + + // get content type + String contentType = responseIncludeWrapper.getContentType(); + if ((contentType == null) || contentType.equals("")) { + contentType = config.getServletContext().getMimeType(req.getRequestURI()); + if ((contentType == null) || contentType.equals("")) { + contentType = "text/html"; + } + } + if (contentType.indexOf(";") > -1) + { + contentType = contentType.substring(0, contentType.indexOf(";")); + } + + // is this an allowed type for SSI processing? + if (allowAllContentTypes || contentTypes.contains(contentType)) { + + // set up SSI processing + SSIExternalResolver ssiExternalResolver = new SSIServletExternalResolver( + config.getServletContext(), req, res, isVirtualWebappRelative, debug); + SSIProcessor ssiProcessor = new SSIProcessor(ssiExternalResolver, + debug); + + // prepare readers/writers + Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes)); + ByteArrayOutputStream ssiout = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(ssiout)); + + // do SSI processing + long lastModified = ssiProcessor.process(reader, responseIncludeWrapper.getLastModified(), writer); + + // set output bytes + writer.flush(); + bytes = ssiout.toByteArray(); + + // override headers + if (expires != null) { + res.setDateHeader("Expires", (new java.util.Date()).getTime() + + expires.longValue() * 1000); + } + if (lastModified > 0) { + res.setDateHeader("Last-Modified", lastModified); + } + } + + // write output + try { + OutputStream out = res.getOutputStream(); + out.write(bytes); + } catch (Throwable t) { + Writer out = res.getWriter(); + out.write(new String(bytes)); + } + } + + + public void destroy() + { + } +}