SSIFilter.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.util.regex.Matcher;
import java.util.regex.Pattern;
import jakarta.servlet.FilterChain;
import jakarta.servlet.GenericFilter;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Filter to process SSI requests within a webpage. Mapped to a content types from within web.xml.
*
* @author David Becker
*
* @see org.apache.catalina.ssi.SSIServlet
*/
public class SSIFilter extends GenericFilter {
private static final long serialVersionUID = 1L;
/** Debug level for this servlet. */
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;
/** regex pattern to match when evaluating content types */
protected Pattern contentTypeRegEx = null;
/** default pattern for ssi filter content type matching */
protected final Pattern shtmlRegEx = Pattern.compile("text/x-server-parsed-html(;.*)?");
/** Allow exec (normally blocked for security) */
protected boolean allowExec = false;
@Override
public void init() throws ServletException {
if (getInitParameter("debug") != null) {
debug = Integer.parseInt(getInitParameter("debug"));
}
if (getInitParameter("contentType") != null) {
contentTypeRegEx = Pattern.compile(getInitParameter("contentType"));
} else {
contentTypeRegEx = shtmlRegEx;
}
isVirtualWebappRelative = Boolean.parseBoolean(getInitParameter("isVirtualWebappRelative"));
if (getInitParameter("expires") != null) {
expires = Long.valueOf(getInitParameter("expires"));
}
allowExec = Boolean.parseBoolean(getInitParameter("allowExec"));
if (debug > 0) {
getServletContext().log("SSIFilter.init() SSI invoker started with 'debug'=" + debug);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// cast once
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// 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();
// is this an allowed type for SSI processing?
if (contentType != null && contentTypeRegEx.matcher(contentType).matches()) {
String encoding = res.getCharacterEncoding();
// set up SSI processing
SSIExternalResolver ssiExternalResolver = new SSIServletExternalResolver(getServletContext(), req, res,
isVirtualWebappRelative, debug, encoding);
SSIProcessor ssiProcessor = new SSIProcessor(ssiExternalResolver, debug, allowExec);
// prepare readers/writers
Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes), encoding);
ByteArrayOutputStream ssiout = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(ssiout, encoding));
// 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);
}
res.setContentLength(bytes.length);
Matcher shtmlMatcher = shtmlRegEx.matcher(responseIncludeWrapper.getContentType());
if (shtmlMatcher.matches()) {
// Convert SHTML mime type to ordinary HTML mime type but preserve
// encoding, if any.
String enc = shtmlMatcher.group(1);
res.setContentType("text/html" + ((enc != null) ? enc : ""));
}
}
// write output
OutputStream out = null;
try {
out = res.getOutputStream();
} catch (IllegalStateException e) {
// Ignore, will try to use a writer
}
if (out == null) {
res.getWriter().write(new String(bytes));
} else {
out.write(bytes);
}
}
}