blob: 40ade941759359e1b38e1d83cab9051fb34b015a [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;
}
}
}