You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by am...@apache.org on 2001/03/27 22:36:50 UTC
cvs commit: jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/servlets SsiInvokerServlet.java
amyroh 01/03/27 12:36:50
Added: catalina/src/share/org/apache/catalina/servlets
SsiInvokerServlet.java
Log:
Servlet to process SSI requests
Revision Changes Path
1.1 jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/servlets/SsiInvokerServlet.java
Index: SsiInvokerServlet.java
===================================================================
/*
* SsiInvokerServlet.java
* $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/servlets/SsiInvokerServlet.java,v 1.1 2001/03/27 20:36:50 amyroh Exp $
* $Revision: 1.1 $
* $Date: 2001/03/27 20:36:50 $
*
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.catalina.servlets;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Date;
import java.util.TimeZone;
import java.util.Locale;
import java.util.StringTokenizer;
import java.text.SimpleDateFormat;
import javax.servlet.ServletException;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.naming.directory.DirContext;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.NamingException;
import javax.naming.InitialContext;
import org.apache.catalina.Globals;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.ssi.SsiCommand;
import org.apache.catalina.util.ssi.SsiMediator;
import org.apache.catalina.util.ssi.ServletOutputStreamWrapper;
import org.apache.naming.resources.Resource;
import org.apache.naming.resources.ResourceAttributes;
/**
* Servlet to process SSI requests within a webpage.
* Mapped to a path from within web.xml.
*
* @author Bip Thelin
* @version $Revision: 1.1 $, $Date: 2001/03/27 20:36:50 $
*/
public final class SsiInvokerServlet extends HttpServlet {
/** Debug level for this servlet. */
private int debug = 0;
/** Should the output be buffered. */
private boolean buffered = false;
/** Expiration time in seconds for the doc. */
private Long expires = null;
/** The Mediator object for the SsiCommands. */
private static SsiMediator ssiMediator = null;
/** JNDI resources name. */
protected static final String RESOURCES_JNDI_NAME = "java:/comp/Resources";
/** The set of SimpleDateFormat formats to use in getDateHeader(). */
protected static final SimpleDateFormat[] formats = {
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
};
protected final static TimeZone gmtZone = TimeZone.getTimeZone("GMT");
/** The start pattern */
private final static byte[] bStart = {
(byte)'<',(byte)'!',(byte)'-',(byte)'-',(byte)'#'
};
/** The end pattern */
private final static byte[] bEnd = {
(byte)'-',(byte)'-',(byte)'>'
};
static {
formats[0].setTimeZone(gmtZone);
formats[1].setTimeZone(gmtZone);
formats[2].setTimeZone(gmtZone);
}
//----------------- Public methods.
/**
* Initialize this servlet.
* @exception ServletException if an error occurs
*/
public void init() throws ServletException {
String value = null;
try {
value = getServletConfig().getInitParameter("debug");
debug = Integer.parseInt(value);
} catch (Throwable t) {
;
}
try {
// adapted from JSSI
value = getServletConfig().getInitParameter("expires");
expires = Long.valueOf(value);
} catch (NumberFormatException e) {
expires = null;
log("Invalid format for expires initParam; expected integer (seconds)");
} catch (Throwable t) {
;
}
try {
value = getServletConfig().getInitParameter("buffered");
buffered = Integer.parseInt(value) > 0 ? true : false;
} catch (Throwable t) {
;
}
if (debug > 0)
log("SsiInvokerServlet.init() SSI invoker started with 'debug'="
+ debug);
}
/**
* Process and forward the GET request
* to our <code>requestHandler()</code>.
*
* @param req a value of type 'HttpServletRequest'
* @param res a value of type 'HttpServletResponse'
* @exception IOException if an error occurs
* @exception ServletException if an error occurs
*/
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
if (debug > 0)
log("SsiInvokerServlet.doGet()");
requestHandler(req, res);
}
/**
* Process and forward the POST request
* to our <code>requestHandler()</code>.
*
* @param req a value of type 'HttpServletRequest'
* @param res a value of type 'HttpServletResponse'
* @exception IOException if an error occurs
* @exception ServletException if an error occurs
*/
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
if (debug > 0)
log("SsiInvokerServlet.doPut()");
requestHandler(req, res);
}
//----------------- Private methods.
/**
* Process our request and locate right SSI command.
* @param req a value of type 'HttpServletRequest'
* @param res a value of type 'HttpServletResponse'
*/
private void requestHandler(HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException {
DirContext resources = getResources();
ServletContext servletContext = getServletContext();
String path = getRelativePath(req);
ResourceInfo resourceInfo = new ResourceInfo(path, resources);
if (debug > 0)
log("SsiInvokerServlet.requestHandler()\n" +
"Serving " + (buffered ? "buffered " : "unbuffered ") +
"resource '" + path + "'");
// Exclude any resource in the /WEB-INF and /META-INF subdirectories
// (the "toUpperCase()" avoids problems on Windows systems)
if ((path == null)
|| path.toUpperCase().startsWith("/WEB-INF")
|| path.toUpperCase().startsWith("/META-INF")) {
res.sendError(res.SC_NOT_FOUND, path);
return;
}
if (!resourceInfo.exists) {
res.sendError(res.SC_NOT_FOUND, path);
return;
}
if (expires != null) {
res.setDateHeader("Expires", (
new java.util.Date()).getTime() + expires.longValue() * 1000);
}
OutputStream out = null;
InputStream resourceInputStream =
servletContext.getResourceAsStream(path);
InputStream in = new BufferedInputStream(resourceInputStream, 4096);
ByteArrayOutputStream soonOut =
new ByteArrayOutputStream((int)resourceInfo.length);
StringBuffer command = new StringBuffer();
byte buf[] = new byte[4096];
int len = 0, bIdx = 0;
char ch;
boolean inside = false;
SsiCommand ssiCommand = null;
String strCmd;
String[] strParamType;
String[] strParam;
if (buffered)
out = (OutputStream)new ServletOutputStreamWrapper();
else
out = res.getOutputStream();
if (ssiMediator == null)
ssiMediator =
new SsiMediator(req, res, out, servletContext, debug, path);
else
ssiMediator.flush(req, res, out, servletContext, path);
while ((len = in.read(buf)) != -1)
soonOut.write(buf, 0, len);
soonOut.close();
byte[] unparsed = soonOut.toByteArray();
soonOut = null; buf = null;
while (bIdx < unparsed.length) {
if (!inside) {
if (unparsed[bIdx] == bStart[0]&&
byteCmp(unparsed, bIdx, bStart)) {
inside = true;
bIdx += bStart.length;
command.delete(0, command.length());
continue;
}
out.write(unparsed[bIdx]);
bIdx++;
} else {
if (unparsed[bIdx] == bEnd[0]&&
byteCmp(unparsed, bIdx, bEnd)) {
inside = false;
bIdx += bEnd.length;
strCmd = parseCmd(command);
strParamType = parseParamType(command, strCmd.length());
strParam = parseParam(command, strCmd.length());
if(debug > 0)
log("Serving SSI resource: " + strCmd);
ssiCommand = ssiMediator.getCommand(strCmd);
if (ssiCommand != null&&
strParamType.length==strParam.length&&
strParamType.length>0) {
if (ssiCommand.isPrintable())
out.write((ssiCommand.getStream(strParamType,
strParam)).getBytes());
else
ssiCommand.process(strParamType, strParam);
} else {
out.write(ssiMediator.getError());
}
continue;
}
command.append((char)unparsed[bIdx]);
bIdx++;
}
}
if (buffered)
((ServletOutputStreamWrapper)out).writeTo(res.getOutputStream());
out = null;
}
/**
* Parse a StringBuffer and take out the command token.
* Called from <code>requestHandler</code>
* @param cmd a value of type 'StringBuffer'
* @return a value of type 'String'
*/
private String parseCmd(StringBuffer cmd)
throws IndexOutOfBoundsException {
String modString = ((cmd.toString()).trim()).toLowerCase();
return modString.substring(0, modString.indexOf(" "));
}
/**
* Parse a StringBuffer and take out the param type token.
* Called from <code>requestHandler</code>
* @param cmd a value of type 'StringBuffer'
* @return a value of type 'String[]'
*/
private String[] parseParamType(StringBuffer cmd, int start) {
int bIdx = start;
int i = 0;
int quotes = 0;
boolean inside = false;
StringBuffer retBuf = new StringBuffer();
while(bIdx < cmd.length()) {
if(!inside) {
while(bIdx < cmd.length()&&isSpace(cmd.charAt(bIdx)))
bIdx++;
if(bIdx>=cmd.length())
break;
inside=!inside;
} else {
while(bIdx < cmd.length()&&cmd.charAt(bIdx)!='=') {
retBuf.append(cmd.charAt(bIdx));
bIdx++;
}
retBuf.append('"');
inside=!inside;
quotes=0;
while(bIdx < cmd.length()&"es!=2) {
if(cmd.charAt(bIdx)=='"'||
cmd.charAt(bIdx)=='\'')
quotes++;
bIdx++;
}
}
}
StringTokenizer str = new StringTokenizer(retBuf.toString(), "\"");
String[] retString = new String[str.countTokens()];
while(str.hasMoreTokens()) {
retString[i++] = str.nextToken().trim();
}
return retString;
}
/**
* Parse a StringBuffer and take out the param token.
* Called from <code>requestHandler</code>
* @param cmd a value of type 'StringBuffer'
* @return a value of type 'String[]'
*/
private String[] parseParam(StringBuffer cmd, int start) {
int bIdx = start;
int i = 0;
int quotes = 0;
boolean inside = false;
StringBuffer retBuf = new StringBuffer();
while(bIdx < cmd.length()) {
if(!inside) {
while(bIdx < cmd.length()&&
cmd.charAt(bIdx)!='"'&&
cmd.charAt(bIdx)!='\'')
bIdx++;
if(bIdx>=cmd.length())
break;
inside=!inside;
} else {
while(bIdx < cmd.length()&&
cmd.charAt(bIdx)!='"'&&
cmd.charAt(bIdx)!='\'') {
retBuf.append(cmd.charAt(bIdx));
bIdx++;
}
retBuf.append('"');
inside=!inside;
}
bIdx++;
}
StringTokenizer str = new StringTokenizer(retBuf.toString(), "\"");
String[] retString = new String[str.countTokens()];
while(str.hasMoreTokens()) {
retString[i++] = str.nextToken();
}
return retString;
}
/**
* Check the input param 'buf' if it matches with the
* pattern 'pattern'. If it does return true.
* @param buf a value of type 'byte[]'
* @param bIdx a value of type 'int'
* @param pattern a value of type 'byte[]'
* @return a value of type 'boolean'
*/
private boolean byteCmp(byte[] buf, int bIdx, byte[] pattern) {
for (int i = 0; i < pattern.length; i++)
if (buf[bIdx + i] != pattern[i])
return false;
return true;
}
private boolean isSpace(char c) {
return c==' '||c=='\n'||c=='\t'||c=='\r';
}
//----------------- Taken from DefaultServlet.java
/**
* Return the relative path associated with this servlet.
* @param request The servlet request we are processing
*/
private String getRelativePath(HttpServletRequest request) {
// Are we being processed by a RequestDispatcher.include()?
if (request.getAttribute("javax.servlet.include.request_uri") != null) {
String result =
(String)request.getAttribute("javax.servlet.include.path_info");
if (result == null)
result =
(String)request.getAttribute("javax.servlet.include.servlet_path");
if ((result == null) || (result.equals("")))
result = "/";
return (result);
}
// No, extract the desired path directly from the request
String result = request.getPathInfo();
if (result == null) {
result = request.getServletPath();
}
if ((result == null) || (result.equals(""))) {
result = "/";
}
return normalize(result);
}
/**
* Return a context-relative path, beginning with a "/", that represents
* the canonical version of the specified path after ".." and "." elements
* are resolved out. If the specified path attempts to go outside the
* boundaries of the current context (i.e. too many ".."
* path elements are present), return <code>null</code> instead.
* @param path Path to be normalized
*/
private String normalize(String path) {
if (path == null)
return null;
// Resolve encoded characters in the normalized path,
// which also handles encoded spaces so we can skip that later.
// Placed at the beginning of the chain so that encoded
// bad stuff(tm) can be caught by the later checks
String normalized = path;
if (normalized.indexOf('%') >= 0)
normalized = RequestUtil.URLDecode(normalized, "UTF8");
if (normalized == null)
return (null);
// Normalize the slashes and add leading slash if necessary
if (normalized.indexOf('\\') >= 0)
normalized = normalized.replace('\\', '/');
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return (null); // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) +
normalized.substring(index + 3);
}
// Return the normalized path that we have completed
return (normalized);
}
/**
* Get resources. This method will try to retrieve the resources through
* JNDI first, then in the servlet context if JNDI has failed
* (it could be disabled). It will return null.
* @return A JNDI DirContext, or null.
*/
private DirContext getResources() {
// First : try JNDI
try {
return (DirContext)new InitialContext().lookup(RESOURCES_JNDI_NAME);
} catch (NamingException e) {
// Failed
} catch (ClassCastException e) {
// Failed : Not the right type
}
// If it has failed, try the servlet context
try {
return (DirContext)getServletContext().getAttribute(Globals.RESOURCES_ATTR);
} catch (ClassCastException e) {
// Failed : Not the right type
}
return null;
}
// ---------------------------------------------- ResourceInfo Inner Class
protected class ResourceInfo {
/**
* Constructor.
* @param pathname Path name of the file
*/
public ResourceInfo(String path, DirContext resources) {
set(path, resources);
}
public Object object;
public DirContext directory;
public Resource file;
public Attributes attributes;
public String path;
public long creationDate;
public String httpDate;
public long date;
public long length;
public boolean collection;
public boolean exists;
public DirContext resources;
protected InputStream is;
public void recycle() {
object = null;
directory = null;
file = null;
attributes = null;
path = null;
creationDate = 0;
httpDate = null;
date = 0;
length = -1;
collection = true;
exists = false;
resources = null;
is = null;
}
public void set(String path, DirContext resources) {
recycle();
this.path = path;
this.resources = resources;
exists = true;
try {
object = resources.lookup(path);
if (object instanceof Resource) {
file = (Resource)object;
collection = false;
} else if (object instanceof DirContext) {
directory = (DirContext)object;
collection = true;
} else {
// Don't know how to serve another object type
exists = false;
}
} catch (NamingException e) {
exists = false;
}
if (exists) {
try {
attributes = resources.getAttributes(path);
if (attributes instanceof ResourceAttributes) {
ResourceAttributes tempAttrs = (ResourceAttributes)attributes;
Date tempDate = tempAttrs.getCreationDate();
if (tempDate != null)
creationDate = tempDate.getTime();
tempDate = tempAttrs.getLastModified();
if (tempDate != null) {
date = tempDate.getTime();
httpDate = formats[0].format(tempDate);
} else {
httpDate = formats[0].format(
new Date());
}
length = tempAttrs.getContentLength();
}
} catch (NamingException e) {
// Shouldn't happen, the implementation of the DirContext
// is probably broken
exists = false;
}
}
}
/** Test if the associated resource exists. */
public boolean exists() {
return exists;
}
/** String representation. */
public String toString() {
return path;
}
/** Set IS. */
public void setStream(InputStream is) {
this.is = is;
}
/** Get IS from resource. */
public InputStream getStream() throws IOException {
if (is != null)
return is;
if (file != null)
return (file.streamContent());
else
return null;
}
}
}