blob: 372d7e00c9470a70fe919f9fb2e6a6047f52099c [file] [log] [blame]
/*
* 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.IOException;
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;
/**
* An implementation of SSIExternalResolver that is used with servlets.
*
* @author Dan Sandberg
* @version $Revision$, $Date$
*/
public class SSIServletExternalResolver implements SSIExternalResolver {
protected final String VARIABLE_NAMES[] = {"AUTH_TYPE", "CONTENT_LENGTH",
"CONTENT_TYPE", "DOCUMENT_NAME", "DOCUMENT_URI",
"GATEWAY_INTERFACE", "PATH_INFO", "PATH_TRANSLATED",
"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 HttpServletRequest req;
protected HttpServletResponse res;
protected boolean isVirtualWebappRelative;
protected int debug;
public SSIServletExternalResolver(HttpServlet servlet,
HttpServletRequest req, HttpServletResponse res,
boolean isVirtualWebappRelative, int debug) {
this.servlet = servlet;
this.req = req;
this.res = res;
this.isVirtualWebappRelative = isVirtualWebappRelative;
this.debug = debug;
}
public void log(String message, Throwable throwable) {
//We can't assume that Servlet.log( message, null )
//is the same as Servlet.log( message ), since API
//doesn't seem to say so.
if (throwable != null) {
servlet.log(message, throwable);
} else {
servlet.log(message);
}
}
public void addVariableNames(Collection variableNames) {
for (int i = 0; i < VARIABLE_NAMES.length; i++) {
String variableName = VARIABLE_NAMES[i];
String variableValue = getVariableValue(variableName);
if (variableValue != null) {
variableNames.add(variableName);
}
}
Enumeration e = req.getAttributeNames();
while (e.hasMoreElements()) {
String name = (String)e.nextElement();
if (!isNameReserved(name)) {
variableNames.add(name);
}
}
}
protected Object getReqAttributeIgnoreCase(String targetName) {
Object object = null;
if (!isNameReserved(targetName)) {
object = req.getAttribute(targetName);
if (object == null) {
Enumeration e = req.getAttributeNames();
while (e.hasMoreElements()) {
String name = (String)e.nextElement();
if (targetName.equalsIgnoreCase(name)
&& !isNameReserved(name)) {
object = req.getAttribute(name);
if (object != null) {
break;
}
}
}
}
}
return object;
}
protected boolean isNameReserved(String name) {
return name.startsWith("java.") || name.startsWith("javax.")
|| name.startsWith("sun.");
}
public void setVariableValue(String name, String value) {
if (!isNameReserved(name)) {
req.setAttribute(name, value);
}
}
public String getVariableValue(String name) {
String retVal = null;
Object object = getReqAttributeIgnoreCase(name);
if (object != null) {
retVal = object.toString();
} else {
retVal = getCGIVariable(name);
}
return retVal;
}
protected String getCGIVariable(String name) {
String retVal = null;
if (name.equalsIgnoreCase("AUTH_TYPE")) {
retVal = req.getAuthType();
} else if (name.equalsIgnoreCase("CONTENT_LENGTH")) {
int contentLength = req.getContentLength();
if (contentLength >= 0) {
retVal = Integer.toString(contentLength);
}
} else if (name.equalsIgnoreCase("CONTENT_TYPE")) {
retVal = req.getContentType();
} else if (name.equalsIgnoreCase("DOCUMENT_NAME")) {
String requestURI = req.getRequestURI();
retVal = requestURI.substring(requestURI.lastIndexOf('/') + 1);
} else if (name.equalsIgnoreCase("DOCUMENT_URI")) {
retVal = req.getRequestURI();
} else if (name.equalsIgnoreCase("GATEWAY_INTERFACE")) {
retVal = "CGI/1.1";
} else if (name.equalsIgnoreCase("PATH_INFO")) {
retVal = req.getPathInfo();
} else if (name.equalsIgnoreCase("PATH_TRANSLATED")) {
retVal = req.getPathTranslated();
} else if (name.equalsIgnoreCase("QUERY_STRING")) {
//apache displays this as an empty string rather than (none)
retVal = nullToEmptyString(req.getQueryString());
} else if (name.equalsIgnoreCase("QUERY_STRING_UNESCAPED")) {
String queryString = req.getQueryString();
if (queryString != null) {
retVal = URLDecoder.decode(queryString);
}
} else if (name.equalsIgnoreCase("REMOTE_ADDR")) {
retVal = req.getRemoteAddr();
} else if (name.equalsIgnoreCase("REMOTE_HOST")) {
retVal = req.getRemoteHost();
} else if (name.equalsIgnoreCase("REMOTE_USER")) {
retVal = req.getRemoteUser();
} else if (name.equalsIgnoreCase("REQUEST_METHOD")) {
retVal = req.getMethod();
} else if (name.equalsIgnoreCase("SCRIPT_NAME")) {
retVal = req.getServletPath();
} else if (name.equalsIgnoreCase("SERVER_NAME")) {
retVal = req.getServerName();
} else if (name.equalsIgnoreCase("SERVER_PORT")) {
retVal = Integer.toString(req.getServerPort());
} else if (name.equalsIgnoreCase("SERVER_PROTOCOL")) {
retVal = req.getProtocol();
} else if (name.equalsIgnoreCase("SERVER_SOFTWARE")) {
ServletContext servletContext = servlet.getServletContext();
retVal = servletContext.getServerInfo();
}
return retVal;
}
public Date getCurrentDate() {
return new Date();
}
protected String nullToEmptyString(String string) {
String retVal = string;
if (retVal == null) {
retVal = "";
}
return retVal;
}
protected String getPathWithoutFileName(String servletPath) {
String retVal = null;
int lastSlash = servletPath.lastIndexOf('/');
if (lastSlash >= 0) {
//cut off file namee
retVal = servletPath.substring(0, lastSlash + 1);
}
return retVal;
}
protected String getPathWithoutContext(String servletPath) {
String retVal = null;
int secondSlash = servletPath.indexOf('/', 1);
if (secondSlash >= 0) {
//cut off context
retVal = servletPath.substring(secondSlash);
}
return retVal;
}
protected String getAbsolutePath(String path) throws IOException {
String pathWithoutContext = SSIServletRequestUtil.getRelativePath(req);
String prefix = getPathWithoutFileName(pathWithoutContext);
if (prefix == null) {
throw new IOException("Couldn't remove filename from path: "
+ pathWithoutContext);
}
String fullPath = prefix + path;
String retVal = SSIServletRequestUtil.normalize(fullPath);
if (retVal == null) {
throw new IOException("Normalization yielded null on path: "
+ fullPath);
}
return retVal;
}
protected ServletContextAndPath getServletContextAndPathFromNonVirtualPath(
String nonVirtualPath) throws IOException {
if (nonVirtualPath.startsWith("/") || nonVirtualPath.startsWith("\\")) {
throw new IOException("A non-virtual path can't be absolute: "
+ nonVirtualPath);
}
if (nonVirtualPath.indexOf("../") >= 0) {
throw new IOException("A non-virtual path can't contain '../' : "
+ nonVirtualPath);
}
String path = getAbsolutePath(nonVirtualPath);
ServletContext servletContext = servlet.getServletContext();
ServletContextAndPath csAndP = new ServletContextAndPath(
servletContext, 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);
} else {
String normalized = SSIServletRequestUtil.normalize(virtualPath);
if (isVirtualWebappRelative) {
path = normalized;
} else {
servletContext = servletContext.getContext(normalized);
if (servletContext == null) {
throw new IOException("Couldn't get context for path: "
+ normalized);
}
//If it's the root context, then there is no context element
// to remove,
// ie:
// '/file1.shtml' vs '/appName1/file1.shtml'
if (!isRootContext(servletContext)) {
path = getPathWithoutContext(normalized);
if (path == null) {
throw new IOException(
"Couldn't remove context from path: "
+ normalized);
}
} else {
path = normalized;
}
}
}
return new ServletContextAndPath(servletContext, path);
}
//Assumes servletContext is not-null
//Assumes that identity comparison will be true for the same context
//Assuming the above, getContext("/") will be non-null as long as the root
// context is
// accessible.
//If it isn't, then servletContext can't be the root context anyway, hence
// they will
// not match.
protected boolean isRootContext(ServletContext servletContext) {
return servletContext == servletContext.getContext("/");
}
protected ServletContextAndPath getServletContextAndPath(
String originalPath, boolean virtual) throws IOException {
ServletContextAndPath csAndP = null;
if (debug > 0) {
log("SSIServletExternalResolver.getServletContextAndPath( "
+ originalPath + ", " + virtual + ")", null);
}
if (virtual) {
csAndP = getServletContextAndPathFromVirtualPath(originalPath);
} else {
csAndP = getServletContextAndPathFromNonVirtualPath(originalPath);
}
return csAndP;
}
protected URLConnection getURLConnection(String originalPath,
boolean virtual) throws IOException {
ServletContextAndPath csAndP = getServletContextAndPath(originalPath,
virtual);
ServletContext context = csAndP.getServletContext();
String path = csAndP.getPath();
URL url = context.getResource(path);
if (url == null) {
throw new IOException("Context did not contain resource: " + path);
}
URLConnection urlConnection = url.openConnection();
return urlConnection;
}
public long getFileLastModified(String path, boolean virtual)
throws IOException {
long lastModified = 0;
URLConnection urlConnection = getURLConnection(path, virtual);
lastModified = urlConnection.getLastModified();
return lastModified;
}
public long getFileSize(String path, boolean virtual) throws IOException {
long fileSize = -1;
URLConnection urlConnection = getURLConnection(path, virtual);
fileSize = urlConnection.getContentLength();
return fileSize;
}
//We are making lots of unnecessary copies of the included data here. If
// someone ever
// complains that this
//is slow, we should connect the included stream to the print writer that
// SSICommand
// uses.
public String getFileText(String originalPath, boolean virtual)
throws IOException {
try {
ServletContextAndPath csAndP = getServletContextAndPath(
originalPath, virtual);
ServletContext context = csAndP.getServletContext();
String path = csAndP.getPath();
RequestDispatcher rd = context.getRequestDispatcher(path);
if (rd == null) {
throw new IOException(
"Couldn't get request dispatcher for path: " + path);
}
ByteArrayServletOutputStream basos = new ByteArrayServletOutputStream();
ResponseIncludeWrapper responseIncludeWrapper = new ResponseIncludeWrapper(
res, basos);
rd.include(req, responseIncludeWrapper);
//We can't assume the included servlet flushed its output
responseIncludeWrapper.flushOutputStreamOrWriter();
byte[] bytes = basos.toByteArray();
//Assume that the default encoding is what was used to encode the
// bytes.
// Questionable.
String retVal = new String(bytes);
//make an assumption that an empty response is a failure. This is
// a problem
// if a truly empty file
//were included, but not sure how else to tell.
if (retVal.equals("")) {
throw new IOException("Couldn't find file: " + path);
}
return retVal;
} catch (ServletException e) {
throw new IOException("Couldn't include file: " + originalPath
+ " because of ServletException: " + e.getMessage());
}
}
protected class ServletContextAndPath {
protected ServletContext servletContext;
protected String path;
public ServletContextAndPath(ServletContext servletContext, String path) {
this.servletContext = servletContext;
this.path = path;
}
public ServletContext getServletContext() {
return servletContext;
}
public String getPath() {
return path;
}
}
}