| /* |
| * 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; |
| } |
| } |
| } |