This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
/*
* 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.ByteArrayOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
/**
* Class that extends ServletOuputStream, used as a wrapper from within
* <code>SsiInclude</code>
*
* @author Bip Thelin
* @see ServletOutputStream and ByteArrayOutputStream
*/
public class ByteArrayServletOutputStream extends ServletOutputStream {
/**
* Our buffer to hold the stream.
*/
protected final ByteArrayOutputStream buf;
/**
* Construct a new ServletOutputStream.
*/
public ByteArrayServletOutputStream() {
buf = new ByteArrayOutputStream();
}
/**
* @return the byte array.
*/
public byte[] toByteArray() {
return buf.toByteArray();
}
/**
* Write to our buffer.
*
* @param b The parameter to write
*/
@Override
public void write(int b) {
buf.write(b);
}
/**
* TODO SERVLET 3.1
*/
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
/**
* TODO SERVLET 3.1
*/
@Override
public void setWriteListener(WriteListener listener) {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,442 @@
/*
* 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.text.ParseException;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* Represents a parsed expression.
*
* @author Paul Speed
*/
public class ExpressionParseTree {
/**
* Contains the current set of completed nodes. This is a workspace for the
* parser.
*/
private final LinkedList<Node> nodeStack = new LinkedList<>();
/**
* Contains operator nodes that don't yet have values. This is a workspace
* for the parser.
*/
private final LinkedList<OppNode> oppStack = new LinkedList<>();
/**
* The root node after the expression has been parsed.
*/
private Node root;
/**
* The SSIMediator to use when evaluating the expressions.
*/
private final SSIMediator ssiMediator;
/**
* Creates a new parse tree for the specified expression.
* @param expr The expression string
* @param ssiMediator Used to evaluated the expressions
* @throws ParseException a parsing error occurred
*/
public ExpressionParseTree(String expr, SSIMediator ssiMediator)
throws ParseException {
this.ssiMediator = ssiMediator;
parseExpression(expr);
}
/**
* Evaluates the tree and returns true or false. The specified SSIMediator
* is used to resolve variable references.
* @return the evaluation result
*/
public boolean evaluateTree() {
return root.evaluate();
}
/**
* Pushes a new operator onto the opp stack, resolving existing opps as
* needed.
* @param node The operator node
*/
private void pushOpp(OppNode node) {
// If node is null then it's just a group marker
if (node == null) {
oppStack.add(0, node);
return;
}
while (true) {
if (oppStack.size() == 0) break;
OppNode top = oppStack.get(0);
// If the top is a spacer then don't pop
// anything
if (top == null) break;
// If the top node has a lower precedence then
// let it stay
if (top.getPrecedence() < node.getPrecedence()) break;
// Remove the top node
oppStack.remove(0);
// Let it fill its branches
top.popValues(nodeStack);
// Stick it on the resolved node stack
nodeStack.add(0, top);
}
// Add the new node to the opp stack
oppStack.add(0, node);
}
/**
* Resolves all pending opp nodes on the stack until the next group marker
* is reached.
*/
private void resolveGroup() {
OppNode top = null;
while ((top = oppStack.remove(0)) != null) {
// Let it fill its branches
top.popValues(nodeStack);
// Stick it on the resolved node stack
nodeStack.add(0, top);
}
}
/**
* Parses the specified expression into a tree of parse nodes.
* @param expr The expression to parse
* @throws ParseException a parsing error occurred
*/
private void parseExpression(String expr) throws ParseException {
StringNode currStringNode = null;
// We cheat a little and start an artificial
// group right away. It makes finishing easier.
pushOpp(null);
ExpressionTokenizer et = new ExpressionTokenizer(expr);
while (et.hasMoreTokens()) {
int token = et.nextToken();
if (token != ExpressionTokenizer.TOKEN_STRING)
currStringNode = null;
switch (token) {
case ExpressionTokenizer.TOKEN_STRING :
if (currStringNode == null) {
currStringNode = new StringNode(et.getTokenValue());
nodeStack.add(0, currStringNode);
} else {
// Add to the existing
currStringNode.value.append(" ");
currStringNode.value.append(et.getTokenValue());
}
break;
case ExpressionTokenizer.TOKEN_AND :
pushOpp(new AndNode());
break;
case ExpressionTokenizer.TOKEN_OR :
pushOpp(new OrNode());
break;
case ExpressionTokenizer.TOKEN_NOT :
pushOpp(new NotNode());
break;
case ExpressionTokenizer.TOKEN_EQ :
pushOpp(new EqualNode());
break;
case ExpressionTokenizer.TOKEN_NOT_EQ :
pushOpp(new NotNode());
// Sneak the regular node in. The NOT will
// be resolved when the next opp comes along.
oppStack.add(0, new EqualNode());
break;
case ExpressionTokenizer.TOKEN_RBRACE :
// Closeout the current group
resolveGroup();
break;
case ExpressionTokenizer.TOKEN_LBRACE :
// Push a group marker
pushOpp(null);
break;
case ExpressionTokenizer.TOKEN_GE :
pushOpp(new NotNode());
// Similar strategy to NOT_EQ above, except this
// is NOT less than
oppStack.add(0, new LessThanNode());
break;
case ExpressionTokenizer.TOKEN_LE :
pushOpp(new NotNode());
// Similar strategy to NOT_EQ above, except this
// is NOT greater than
oppStack.add(0, new GreaterThanNode());
break;
case ExpressionTokenizer.TOKEN_GT :
pushOpp(new GreaterThanNode());
break;
case ExpressionTokenizer.TOKEN_LT :
pushOpp(new LessThanNode());
break;
case ExpressionTokenizer.TOKEN_END :
break;
}
}
// Finish off the rest of the opps
resolveGroup();
if (nodeStack.size() == 0) {
throw new ParseException("No nodes created.", et.getIndex());
}
if (nodeStack.size() > 1) {
throw new ParseException("Extra nodes created.", et.getIndex());
}
if (oppStack.size() != 0) {
throw new ParseException("Unused opp nodes exist.", et.getIndex());
}
root = nodeStack.get(0);
}
/**
* A node in the expression parse tree.
*/
private abstract class Node {
/**
* @return {@code true} if the node evaluates to true.
*/
public abstract boolean evaluate();
}
/**
* A node the represents a String value
*/
private class StringNode extends Node {
StringBuilder value;
String resolved = null;
public StringNode(String value) {
this.value = new StringBuilder(value);
}
/**
* Resolves any variable references and returns the value string.
*
* @return the value string
*/
public String getValue() {
if (resolved == null)
resolved = ssiMediator.substituteVariables(value.toString());
return resolved;
}
/**
* Returns true if the string is not empty.
*/
@Override
public boolean evaluate() {
return !(getValue().length() == 0);
}
@Override
public String toString() {
return value.toString();
}
}
private static final int PRECEDENCE_NOT = 5;
private static final int PRECEDENCE_COMPARE = 4;
private static final int PRECEDENCE_LOGICAL = 1;
/**
* A node implementation that represents an operation.
*/
private abstract class OppNode extends Node {
/**
* The left branch.
*/
Node left;
/**
* The right branch.
*/
Node right;
/**
* @return a precedence level suitable for comparison to other OppNode
* preference levels.
*/
public abstract int getPrecedence();
/**
* Lets the node pop its own branch nodes off the front of the
* specified list. The default pulls two.
*
* @param values The list from which to pop the values
*/
public void popValues(List<Node> values) {
right = values.remove(0);
left = values.remove(0);
}
}
private final class NotNode extends OppNode {
@Override
public boolean evaluate() {
return !left.evaluate();
}
@Override
public int getPrecedence() {
return PRECEDENCE_NOT;
}
/**
* Overridden to pop only one value.
*/
@Override
public void popValues(List<Node> values) {
left = values.remove(0);
}
@Override
public String toString() {
return left + " NOT";
}
}
private final class AndNode extends OppNode {
@Override
public boolean evaluate() {
if (!left.evaluate()) // Short circuit
return false;
return right.evaluate();
}
@Override
public int getPrecedence() {
return PRECEDENCE_LOGICAL;
}
@Override
public String toString() {
return left + " " + right + " AND";
}
}
private final class OrNode extends OppNode {
@Override
public boolean evaluate() {
if (left.evaluate()) // Short circuit
return true;
return right.evaluate();
}
@Override
public int getPrecedence() {
return PRECEDENCE_LOGICAL;
}
@Override
public String toString() {
return left + " " + right + " OR";
}
}
private abstract class CompareNode extends OppNode {
protected int compareBranches() {
String val1 = ((StringNode)left).getValue();
String val2 = ((StringNode)right).getValue();
int val2Len = val2.length();
if (val2Len > 1 && val2.charAt(0) == '/' &&
val2.charAt(val2Len - 1) == '/') {
// Treat as a regular expression
String expr = val2.substring(1, val2Len - 1);
try {
Pattern pattern = Pattern.compile(expr);
// Regular expressions will only ever be used with EqualNode
// so return zero for equal and non-zero for not equal
if (pattern.matcher(val1).find()) {
return 0;
} else {
return -1;
}
} catch (PatternSyntaxException pse) {
ssiMediator.log("Invalid expression: " + expr, pse);
return 0;
}
}
return val1.compareTo(val2);
}
}
private final class EqualNode extends CompareNode {
@Override
public boolean evaluate() {
return (compareBranches() == 0);
}
@Override
public int getPrecedence() {
return PRECEDENCE_COMPARE;
}
@Override
public String toString() {
return left + " " + right + " EQ";
}
}
private final class GreaterThanNode extends CompareNode {
@Override
public boolean evaluate() {
return (compareBranches() > 0);
}
@Override
public int getPrecedence() {
return PRECEDENCE_COMPARE;
}
@Override
public String toString() {
return left + " " + right + " GT";
}
}
private final class LessThanNode extends CompareNode {
@Override
public boolean evaluate() {
return (compareBranches() < 0);
}
@Override
public int getPrecedence() {
return PRECEDENCE_COMPARE;
}
@Override
public String toString() {
return left + " " + right + " LT";
}
}
}

View File

@@ -0,0 +1,186 @@
/*
* 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;
/**
* Parses an expression string to return the individual tokens. This is
* patterned similar to the StreamTokenizer in the JDK but customized for SSI
* conditional expression parsing.
*
* @author Paul Speed
*/
public class ExpressionTokenizer {
public static final int TOKEN_STRING = 0;
public static final int TOKEN_AND = 1;
public static final int TOKEN_OR = 2;
public static final int TOKEN_NOT = 3;
public static final int TOKEN_EQ = 4;
public static final int TOKEN_NOT_EQ = 5;
public static final int TOKEN_RBRACE = 6;
public static final int TOKEN_LBRACE = 7;
public static final int TOKEN_GE = 8;
public static final int TOKEN_LE = 9;
public static final int TOKEN_GT = 10;
public static final int TOKEN_LT = 11;
public static final int TOKEN_END = 12;
private final char[] expr;
private String tokenVal = null;
private int index;
private final int length;
/**
* Creates a new parser for the specified expression.
* @param expr The expression
*/
public ExpressionTokenizer(String expr) {
this.expr = expr.trim().toCharArray();
this.length = this.expr.length;
}
/**
* @return <code>true</code> if there are more tokens.
*/
public boolean hasMoreTokens() {
return index < length;
}
/**
* @return the current index for error reporting purposes.
*/
public int getIndex() {
return index;
}
protected boolean isMetaChar(char c) {
return Character.isWhitespace(c) || c == '(' || c == ')' || c == '!'
|| c == '<' || c == '>' || c == '|' || c == '&' || c == '=';
}
/**
* @return the next token type and initializes any state variables
* accordingly.
*/
public int nextToken() {
// Skip any leading white space
while (index < length && Character.isWhitespace(expr[index]))
index++;
// Clear the current token val
tokenVal = null;
if (index == length) return TOKEN_END; // End of string
int start = index;
char currentChar = expr[index];
char nextChar = (char)0;
index++;
if (index < length) nextChar = expr[index];
// Check for a known token start
switch (currentChar) {
case '(' :
return TOKEN_LBRACE;
case ')' :
return TOKEN_RBRACE;
case '=' :
return TOKEN_EQ;
case '!' :
if (nextChar == '=') {
index++;
return TOKEN_NOT_EQ;
}
return TOKEN_NOT;
case '|' :
if (nextChar == '|') {
index++;
return TOKEN_OR;
}
break;
case '&' :
if (nextChar == '&') {
index++;
return TOKEN_AND;
}
break;
case '>' :
if (nextChar == '=') {
index++;
return TOKEN_GE; // Greater than or equal
}
return TOKEN_GT; // Greater than
case '<' :
if (nextChar == '=') {
index++;
return TOKEN_LE; // Less than or equal
}
return TOKEN_LT; // Less than
default :
// Otherwise it's a string
break;
}
int end = index;
if (currentChar == '"' || currentChar == '\'') {
// It's a quoted string and the end is the next unescaped quote
char endChar = currentChar;
boolean escaped = false;
start++;
for (; index < length; index++) {
if (expr[index] == '\\' && !escaped) {
escaped = true;
continue;
}
if (expr[index] == endChar && !escaped) break;
escaped = false;
}
end = index;
index++; // Skip the end quote
} else if (currentChar == '/') {
// It's a regular expression and the end is the next unescaped /
char endChar = currentChar;
boolean escaped = false;
for (; index < length; index++) {
if (expr[index] == '\\' && !escaped) {
escaped = true;
continue;
}
if (expr[index] == endChar && !escaped) break;
escaped = false;
}
end = ++index;
} else {
// End is the next whitespace character
for (; index < length; index++) {
if (isMetaChar(expr[index])) break;
}
end = index;
}
// Extract the string from the array
this.tokenVal = new String(expr, start, end - start);
return TOKEN_STRING;
}
/**
* @return the String value of the token if it was type TOKEN_STRING.
* Otherwise null is returned.
*/
public String getTokenValue() {
return tokenVal;
}
}

View File

@@ -0,0 +1,195 @@
/*
* 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.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.tomcat.util.ExceptionUtils;
/**
* An HttpServletResponseWrapper, used from
* <code>SSIServletExternalResolver</code>
*
* @author Bip Thelin
* @author David Becker
*/
public class ResponseIncludeWrapper extends HttpServletResponseWrapper {
/**
* The names of some headers we want to capture.
*/
private static final String LAST_MODIFIED = "last-modified";
private static final DateFormat RFC1123_FORMAT;
private static final String RFC1123_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z";
protected long lastModified = -1;
/**
* Our ServletOutputStream
*/
protected final ServletOutputStream captureServletOutputStream;
protected ServletOutputStream servletOutputStream;
protected PrintWriter printWriter;
static {
RFC1123_FORMAT = new SimpleDateFormat(RFC1123_PATTERN, Locale.US);
RFC1123_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
}
/**
* Initialize our wrapper with the current HttpServletResponse and
* ServletOutputStream.
*
* @param response The response to use
* @param captureServletOutputStream The ServletOutputStream to use
*/
public ResponseIncludeWrapper(HttpServletResponse response,
ServletOutputStream captureServletOutputStream) {
super(response);
this.captureServletOutputStream = captureServletOutputStream;
}
/**
* Flush the servletOutputStream or printWriter ( only one will be non-null )
* This must be called after a requestDispatcher.include, since we can't
* assume that the included servlet flushed its stream.
* @throws IOException an IO error occurred
*/
public void flushOutputStreamOrWriter() throws IOException {
if (servletOutputStream != null) {
servletOutputStream.flush();
}
if (printWriter != null) {
printWriter.flush();
}
}
/**
* Return a printwriter, throws and exception if a OutputStream already
* been returned.
*
* @return a PrintWriter object
* @exception java.io.IOException
* if the outputstream already been called
*/
@Override
public PrintWriter getWriter() throws java.io.IOException {
if (servletOutputStream == null) {
if (printWriter == null) {
setCharacterEncoding(getCharacterEncoding());
printWriter = new PrintWriter(
new OutputStreamWriter(captureServletOutputStream,
getCharacterEncoding()));
}
return printWriter;
}
throw new IllegalStateException();
}
/**
* Return a OutputStream, throws and exception if a printwriter already
* been returned.
*
* @return a OutputStream object
* @exception java.io.IOException
* if the printwriter already been called
*/
@Override
public ServletOutputStream getOutputStream() throws java.io.IOException {
if (printWriter == null) {
if (servletOutputStream == null) {
servletOutputStream = captureServletOutputStream;
}
return servletOutputStream;
}
throw new IllegalStateException();
}
/**
* Returns the value of the <code>last-modified</code> header field. The
* result is the number of milliseconds since January 1, 1970 GMT.
*
* @return the date the resource referenced by this
* <code>ResponseIncludeWrapper</code> was last modified, or -1 if not
* known.
*/
public long getLastModified() {
return lastModified;
}
@Override
public void addDateHeader(String name, long value) {
super.addDateHeader(name, value);
String lname = name.toLowerCase(Locale.ENGLISH);
if (lname.equals(LAST_MODIFIED)) {
lastModified = value;
}
}
@Override
public void addHeader(String name, String value) {
super.addHeader(name, value);
String lname = name.toLowerCase(Locale.ENGLISH);
if (lname.equals(LAST_MODIFIED)) {
try {
synchronized(RFC1123_FORMAT) {
lastModified = RFC1123_FORMAT.parse(value).getTime();
}
} catch (Throwable ignore) {
ExceptionUtils.handleThrowable(ignore);
}
}
}
@Override
public void setDateHeader(String name, long value) {
super.setDateHeader(name, value);
String lname = name.toLowerCase(Locale.ENGLISH);
if (lname.equals(LAST_MODIFIED)) {
lastModified = value;
}
}
@Override
public void setHeader(String name, String value) {
super.setHeader(name, value);
String lname = name.toLowerCase(Locale.ENGLISH);
if (lname.equals(LAST_MODIFIED)) {
try {
synchronized(RFC1123_FORMAT) {
lastModified = RFC1123_FORMAT.parse(value).getTime();
}
} catch (Throwable ignore) {
ExceptionUtils.handleThrowable(ignore);
}
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.PrintWriter;
/**
* The interface that all SSI commands ( SSIEcho, SSIInclude, ...) must
* implement.
*
* @author Bip Thelin
* @author Dan Sandberg
* @author David Becker
*/
public interface SSICommand {
/**
* Write the output of the command to the writer.
*
* @param ssiMediator
* the ssi mediator
* @param commandName
* the name of the actual command ( ie. echo )
* @param paramNames
* The parameter names
* @param paramValues
* The parameter values
* @param writer
* the writer to output to
* @return the most current modified date resulting from any SSI commands
* @throws SSIStopProcessingException
* if SSI processing should be aborted
*/
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer)
throws SSIStopProcessingException;
}

View File

@@ -0,0 +1,139 @@
/*
* 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.PrintWriter;
import java.text.ParseException;
/**
* SSI command that handles all conditional directives.
*
* @author Paul Speed
* @author David Becker
*/
public class SSIConditional implements SSICommand {
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer)
throws SSIStopProcessingException {
// Assume anything using conditionals was modified by it
long lastModified = System.currentTimeMillis();
// Retrieve the current state information
SSIConditionalState state = ssiMediator.getConditionalState();
if ("if".equalsIgnoreCase(commandName)) {
// Do nothing if we are nested in a false branch
// except count it
if (state.processConditionalCommandsOnly) {
state.nestingCount++;
return lastModified;
}
state.nestingCount = 0;
// Evaluate the expression
if (evaluateArguments(paramNames, paramValues, ssiMediator)) {
// No more branches can be taken for this if block
state.branchTaken = true;
} else {
// Do not process this branch
state.processConditionalCommandsOnly = true;
state.branchTaken = false;
}
} else if ("elif".equalsIgnoreCase(commandName)) {
// No need to even execute if we are nested in
// a false branch
if (state.nestingCount > 0) return lastModified;
// If a branch was already taken in this if block
// then disable output and return
if (state.branchTaken) {
state.processConditionalCommandsOnly = true;
return lastModified;
}
// Evaluate the expression
if (evaluateArguments(paramNames, paramValues, ssiMediator)) {
// Turn back on output and mark the branch
state.processConditionalCommandsOnly = false;
state.branchTaken = true;
} else {
// Do not process this branch
state.processConditionalCommandsOnly = true;
state.branchTaken = false;
}
} else if ("else".equalsIgnoreCase(commandName)) {
// No need to even execute if we are nested in
// a false branch
if (state.nestingCount > 0) return lastModified;
// If we've already taken another branch then
// disable output otherwise enable it.
state.processConditionalCommandsOnly = state.branchTaken;
// And in any case, it's safe to say a branch
// has been taken.
state.branchTaken = true;
} else if ("endif".equalsIgnoreCase(commandName)) {
// If we are nested inside a false branch then pop out
// one level on the nesting count
if (state.nestingCount > 0) {
state.nestingCount--;
return lastModified;
}
// Turn output back on
state.processConditionalCommandsOnly = false;
// Reset the branch status for any outer if blocks,
// since clearly we took a branch to have gotten here
// in the first place.
state.branchTaken = true;
} else {
throw new SSIStopProcessingException();
//throw new SsiCommandException( "Not a conditional command:" +
// cmdName );
}
return lastModified;
}
/**
* Retrieves the expression from the specified arguments and peforms the
* necessary evaluation steps.
*/
private boolean evaluateArguments(String[] names, String[] values,
SSIMediator ssiMediator) throws SSIStopProcessingException {
String expr = getExpression(names, values);
if (expr == null) {
throw new SSIStopProcessingException();
//throw new SsiCommandException( "No expression specified." );
}
try {
ExpressionParseTree tree = new ExpressionParseTree(expr,
ssiMediator);
return tree.evaluateTree();
} catch (ParseException e) {
//throw new SsiCommandException( "Error parsing expression." );
throw new SSIStopProcessingException();
}
}
/**
* Returns the "expr" if the arg name is appropriate, otherwise returns
* null.
*/
private String getExpression(String[] paramNames, String[] paramValues) {
if ("expr".equalsIgnoreCase(paramNames[0])) return paramValues[0];
return null;
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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;
/**
* This class is used by SSIMediator and SSIConditional to keep track of state
* information necessary to process the nested conditional commands ( if, elif,
* else, endif ).
*
* @author Dan Sandberg
* @author Paul Speed
*/
class SSIConditionalState {
/**
* Set to true if the current conditional has already been completed, i.e.:
* a branch was taken.
*/
boolean branchTaken = false;
/**
* Counts the number of nested false branches.
*/
int nestingCount = 0;
/**
* Set to true if only conditional commands ( if, elif, else, endif )
* should be processed.
*/
boolean processConditionalCommandsOnly = false;
}

View File

@@ -0,0 +1,59 @@
/*
* 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.PrintWriter;
/**
* Implements the Server-side #exec command
*
* @author Bip Thelin
* @author Paul Speed
* @author Dan Sandberg
* @author David Becker
*/
public final class SSIConfig implements SSICommand {
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer) {
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
String paramValue = paramValues[i];
String substitutedValue = ssiMediator
.substituteVariables(paramValue);
if (paramName.equalsIgnoreCase("errmsg")) {
ssiMediator.setConfigErrMsg(substitutedValue);
} else if (paramName.equalsIgnoreCase("sizefmt")) {
ssiMediator.setConfigSizeFmt(substitutedValue);
} else if (paramName.equalsIgnoreCase("timefmt")) {
ssiMediator.setConfigTimeFmt(substitutedValue);
} else {
ssiMediator.log("#config--Invalid attribute: " + paramName);
//We need to fetch this value each time, since it may change
// during the
// loop
String configErrMsg = ssiMediator.getConfigErrMsg();
writer.write(configErrMsg);
}
}
// Setting config options doesn't really change the page
return 0;
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.PrintWriter;
/**
* Return the result associated with the supplied Server Variable.
*
* @author Bip Thelin
* @author Paul Speed
* @author Dan Sandberg
* @author David Becker
*/
public class SSIEcho implements SSICommand {
protected static final String DEFAULT_ENCODING = SSIMediator.ENCODING_ENTITY;
protected static final String MISSING_VARIABLE_VALUE = "(none)";
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer) {
String encoding = DEFAULT_ENCODING;
String originalValue = null;
String errorMessage = ssiMediator.getConfigErrMsg();
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
String paramValue = paramValues[i];
if (paramName.equalsIgnoreCase("var")) {
originalValue = paramValue;
} else if (paramName.equalsIgnoreCase("encoding")) {
if (isValidEncoding(paramValue)) {
encoding = paramValue;
} else {
ssiMediator.log("#echo--Invalid encoding: " + paramValue);
writer.write(ssiMediator.encode(errorMessage, SSIMediator.ENCODING_ENTITY));
}
} else {
ssiMediator.log("#echo--Invalid attribute: " + paramName);
writer.write(ssiMediator.encode(errorMessage, SSIMediator.ENCODING_ENTITY));
}
}
String variableValue = ssiMediator.getVariableValue(originalValue, encoding);
if (variableValue == null) {
variableValue = MISSING_VARIABLE_VALUE;
}
writer.write(variableValue);
return System.currentTimeMillis();
}
protected boolean isValidEncoding(String encoding) {
return encoding.equalsIgnoreCase(SSIMediator.ENCODING_URL)
|| encoding.equalsIgnoreCase(SSIMediator.ENCODING_ENTITY)
|| encoding.equalsIgnoreCase(SSIMediator.ENCODING_NONE);
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import org.apache.catalina.util.IOTools;
/**
* Implements the Server-side #exec command
*
* @author Bip Thelin
* @author Amy Roh
* @author Paul Speed
* @author Dan Sandberg
* @author David Becker
*/
public class SSIExec implements SSICommand {
protected final SSIInclude ssiInclude = new SSIInclude();
protected static final int BUFFER_SIZE = 1024;
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer) {
long lastModified = 0;
String configErrMsg = ssiMediator.getConfigErrMsg();
String paramName = paramNames[0];
String paramValue = paramValues[0];
String substitutedValue = ssiMediator.substituteVariables(paramValue);
if (paramName.equalsIgnoreCase("cgi")) {
lastModified = ssiInclude.process(ssiMediator, "include",
new String[]{"virtual"}, new String[]{substitutedValue},
writer);
} else if (paramName.equalsIgnoreCase("cmd")) {
boolean foundProgram = false;
try {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(substitutedValue);
foundProgram = true;
BufferedReader stdOutReader = new BufferedReader(
new InputStreamReader(proc.getInputStream()));
BufferedReader stdErrReader = new BufferedReader(
new InputStreamReader(proc.getErrorStream()));
char[] buf = new char[BUFFER_SIZE];
IOTools.flow(stdErrReader, writer, buf);
IOTools.flow(stdOutReader, writer, buf);
proc.waitFor();
lastModified = System.currentTimeMillis();
} catch (InterruptedException e) {
ssiMediator.log("Couldn't exec file: " + substitutedValue, e);
writer.write(configErrMsg);
} catch (IOException e) {
if (!foundProgram) {
//apache doesn't output an error message if it can't find
// a program
}
ssiMediator.log("Couldn't exec file: " + substitutedValue, e);
}
}
return lastModified;
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.IOException;
import java.util.Collection;
import java.util.Date;
/**
* Interface used by SSIMediator to talk to the 'outside world' ( usually a
* servlet )
*
* @author Dan Sandberg
*/
public interface SSIExternalResolver {
/**
* Adds any external variables to the variableNames collection.
*
* @param variableNames
* the collection to add to
*/
public void addVariableNames(Collection<String> variableNames);
public String getVariableValue(String name);
/**
* Set the named variable to the specified value. If value is null, then
* the variable will be removed ( ie. a call to getVariableValue will
* return null )
*
* @param name
* of the variable
* @param value
* of the variable
*/
public void setVariableValue(String name, String value);
/**
* Returns the current date. This is useful for putting the SSI stuff in a
* regression test. Since you can make the current date a constant, it
* makes testing easier since the output won't change.
*
* @return the data
*/
public Date getCurrentDate();
public long getFileSize(String path, boolean virtual) throws IOException;
public long getFileLastModified(String path, boolean virtual)
throws IOException;
public String getFileText(String path, boolean virtual) throws IOException;
public void log(String message, Throwable throwable);
}

View File

@@ -0,0 +1,175 @@
/*
* 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 javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.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 implements Filter {
protected FilterConfig config = null;
/** 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(FilterConfig config) throws ServletException {
this.config = config;
if (config.getInitParameter("debug") != null) {
debug = Integer.parseInt(config.getInitParameter("debug"));
}
if (config.getInitParameter("contentType") != null) {
contentTypeRegEx = Pattern.compile(config.getInitParameter("contentType"));
} else {
contentTypeRegEx = shtmlRegEx;
}
isVirtualWebappRelative =
Boolean.parseBoolean(config.getInitParameter("isVirtualWebappRelative"));
if (config.getInitParameter("expires") != null)
expires = Long.valueOf(config.getInitParameter("expires"));
allowExec = Boolean.parseBoolean(config.getInitParameter("allowExec"));
if (debug > 0)
config.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(config.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);
}
}
@Override
public void destroy() {
// NOOP
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Locale;
import org.apache.catalina.util.Strftime;
/**
* Implements the Server-side #flastmod command
*
* @author Bip Thelin
* @author Paul Speed
* @author Dan Sandberg
* @author David Becker
*/
public final class SSIFlastmod implements SSICommand {
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer) {
long lastModified = 0;
String configErrMsg = ssiMediator.getConfigErrMsg();
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
String paramValue = paramValues[i];
String substitutedValue = ssiMediator
.substituteVariables(paramValue);
try {
if (paramName.equalsIgnoreCase("file")
|| paramName.equalsIgnoreCase("virtual")) {
boolean virtual = paramName.equalsIgnoreCase("virtual");
lastModified = ssiMediator.getFileLastModified(
substitutedValue, virtual);
Date date = new Date(lastModified);
String configTimeFmt = ssiMediator.getConfigTimeFmt();
writer.write(formatDate(date, configTimeFmt));
} else {
ssiMediator.log("#flastmod--Invalid attribute: "
+ paramName);
writer.write(configErrMsg);
}
} catch (IOException e) {
ssiMediator.log(
"#flastmod--Couldn't get last modified for file: "
+ substitutedValue, e);
writer.write(configErrMsg);
}
}
return lastModified;
}
protected String formatDate(Date date, String configTimeFmt) {
Strftime strftime = new Strftime(configTimeFmt, Locale.US);
return strftime.format(date);
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.IOException;
import java.io.PrintWriter;
import java.text.DecimalFormat;
/**
* Implements the Server-side #fsize command
*
* @author Bip Thelin
* @author Paul Speed
* @author Dan Sandberg
* @author David Becker
*/
public final class SSIFsize implements SSICommand {
static final int ONE_KILOBYTE = 1024;
static final int ONE_MEGABYTE = 1024 * 1024;
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer) {
long lastModified = 0;
String configErrMsg = ssiMediator.getConfigErrMsg();
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
String paramValue = paramValues[i];
String substitutedValue = ssiMediator
.substituteVariables(paramValue);
try {
if (paramName.equalsIgnoreCase("file")
|| paramName.equalsIgnoreCase("virtual")) {
boolean virtual = paramName.equalsIgnoreCase("virtual");
lastModified = ssiMediator.getFileLastModified(
substitutedValue, virtual);
long size = ssiMediator.getFileSize(substitutedValue,
virtual);
String configSizeFmt = ssiMediator.getConfigSizeFmt();
writer.write(formatSize(size, configSizeFmt));
} else {
ssiMediator.log("#fsize--Invalid attribute: " + paramName);
writer.write(configErrMsg);
}
} catch (IOException e) {
ssiMediator.log("#fsize--Couldn't get size for file: "
+ substitutedValue, e);
writer.write(configErrMsg);
}
}
return lastModified;
}
public String repeat(char aChar, int numChars) {
if (numChars < 0) {
throw new IllegalArgumentException("Num chars can't be negative");
}
StringBuilder buf = new StringBuilder();
for (int i = 0; i < numChars; i++) {
buf.append(aChar);
}
return buf.toString();
}
public String padLeft(String str, int maxChars) {
String result = str;
int charsToAdd = maxChars - str.length();
if (charsToAdd > 0) {
result = repeat(' ', charsToAdd) + str;
}
return result;
}
//We try to mimic Apache here, as we do everywhere
//All the 'magic' numbers are from the util_script.c Apache source file.
protected String formatSize(long size, String format) {
String retString = "";
if (format.equalsIgnoreCase("bytes")) {
DecimalFormat decimalFormat = new DecimalFormat("#,##0");
retString = decimalFormat.format(size);
} else {
if (size < 0) {
retString = "-";
} else if (size == 0) {
retString = "0k";
} else if (size < ONE_KILOBYTE) {
retString = "1k";
} else if (size < ONE_MEGABYTE) {
retString = Long.toString((size + 512) / ONE_KILOBYTE);
retString += "k";
} else if (size < 99 * ONE_MEGABYTE) {
DecimalFormat decimalFormat = new DecimalFormat("0.0M");
retString = decimalFormat.format(size / (double)ONE_MEGABYTE);
} else {
retString = Long.toString((size + (529 * ONE_KILOBYTE))
/ ONE_MEGABYTE);
retString += "M";
}
retString = padLeft(retString, 5);
}
return retString;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.IOException;
import java.io.PrintWriter;
/**
* Implements the Server-side #include command
*
* @author Bip Thelin
* @author Paul Speed
* @author Dan Sandberg
* @author David Becker
*/
public final class SSIInclude implements SSICommand {
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer) {
long lastModified = 0;
String configErrMsg = ssiMediator.getConfigErrMsg();
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
String paramValue = paramValues[i];
String substitutedValue = ssiMediator
.substituteVariables(paramValue);
try {
if (paramName.equalsIgnoreCase("file")
|| paramName.equalsIgnoreCase("virtual")) {
boolean virtual = paramName.equalsIgnoreCase("virtual");
lastModified = ssiMediator.getFileLastModified(
substitutedValue, virtual);
String text = ssiMediator.getFileText(substitutedValue,
virtual);
writer.write(text);
} else {
ssiMediator.log("#include--Invalid attribute: "
+ paramName);
writer.write(configErrMsg);
}
} catch (IOException e) {
ssiMediator.log("#include--Couldn't include file: "
+ substitutedValue, e);
writer.write(configErrMsg);
}
}
return lastModified;
}
}

View File

@@ -0,0 +1,340 @@
/*
* 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.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import org.apache.catalina.util.Strftime;
import org.apache.catalina.util.URLEncoder;
import org.apache.tomcat.util.security.Escape;
/**
* Allows the different SSICommand implementations to share data/talk to each
* other
*
* @author Bip Thelin
* @author Amy Roh
* @author Paul Speed
* @author Dan Sandberg
* @author David Becker
*/
public class SSIMediator {
protected static final String ENCODING_NONE = "none";
protected static final String ENCODING_ENTITY = "entity";
protected static final String ENCODING_URL = "url";
protected static final String DEFAULT_CONFIG_ERR_MSG = "[an error occurred while processing this directive]";
protected static final String DEFAULT_CONFIG_TIME_FMT = "%A, %d-%b-%Y %T %Z";
protected static final String DEFAULT_CONFIG_SIZE_FMT = "abbrev";
protected String configErrMsg = DEFAULT_CONFIG_ERR_MSG;
protected String configTimeFmt = DEFAULT_CONFIG_TIME_FMT;
protected String configSizeFmt = DEFAULT_CONFIG_SIZE_FMT;
protected final String className = getClass().getName();
protected final SSIExternalResolver ssiExternalResolver;
protected final long lastModifiedDate;
protected Strftime strftime;
protected final SSIConditionalState conditionalState = new SSIConditionalState();
public SSIMediator(SSIExternalResolver ssiExternalResolver,
long lastModifiedDate) {
this.ssiExternalResolver = ssiExternalResolver;
this.lastModifiedDate = lastModifiedDate;
setConfigTimeFmt(DEFAULT_CONFIG_TIME_FMT, true);
}
public void setConfigErrMsg(String configErrMsg) {
this.configErrMsg = configErrMsg;
}
public void setConfigTimeFmt(String configTimeFmt) {
setConfigTimeFmt(configTimeFmt, false);
}
public void setConfigTimeFmt(String configTimeFmt, boolean fromConstructor) {
this.configTimeFmt = configTimeFmt;
this.strftime = new Strftime(configTimeFmt, Locale.US);
//Variables like DATE_LOCAL, DATE_GMT, and LAST_MODIFIED need to be
// updated when
//the timefmt changes. This is what Apache SSI does.
setDateVariables(fromConstructor);
}
public void setConfigSizeFmt(String configSizeFmt) {
this.configSizeFmt = configSizeFmt;
}
public String getConfigErrMsg() {
return configErrMsg;
}
public String getConfigTimeFmt() {
return configTimeFmt;
}
public String getConfigSizeFmt() {
return configSizeFmt;
}
public SSIConditionalState getConditionalState() {
return conditionalState;
}
public Collection<String> getVariableNames() {
Set<String> variableNames = new HashSet<>();
//These built-in variables are supplied by the mediator ( if not
// over-written by
// the user ) and always exist
variableNames.add("DATE_GMT");
variableNames.add("DATE_LOCAL");
variableNames.add("LAST_MODIFIED");
ssiExternalResolver.addVariableNames(variableNames);
//Remove any variables that are reserved by this class
Iterator<String> iter = variableNames.iterator();
while (iter.hasNext()) {
String name = iter.next();
if (isNameReserved(name)) {
iter.remove();
}
}
return variableNames;
}
public long getFileSize(String path, boolean virtual) throws IOException {
return ssiExternalResolver.getFileSize(path, virtual);
}
public long getFileLastModified(String path, boolean virtual)
throws IOException {
return ssiExternalResolver.getFileLastModified(path, virtual);
}
public String getFileText(String path, boolean virtual) throws IOException {
return ssiExternalResolver.getFileText(path, virtual);
}
protected boolean isNameReserved(String name) {
return name.startsWith(className + ".");
}
public String getVariableValue(String variableName) {
return getVariableValue(variableName, ENCODING_NONE);
}
public void setVariableValue(String variableName, String variableValue) {
if (!isNameReserved(variableName)) {
ssiExternalResolver.setVariableValue(variableName, variableValue);
}
}
public String getVariableValue(String variableName, String encoding) {
String lowerCaseVariableName = variableName.toLowerCase(Locale.ENGLISH);
String variableValue = null;
if (!isNameReserved(lowerCaseVariableName)) {
//Try getting it externally first, if it fails, try getting the
// 'built-in'
// value
variableValue = ssiExternalResolver.getVariableValue(variableName);
if (variableValue == null) {
variableName = variableName.toUpperCase(Locale.ENGLISH);
variableValue = ssiExternalResolver
.getVariableValue(className + "." + variableName);
}
if (variableValue != null) {
variableValue = encode(variableValue, encoding);
}
}
return variableValue;
}
/**
* Applies variable substitution to the specified String and returns the
* new resolved string.
* @param val The value which should be checked
* @return the value after variable substitution
*/
public String substituteVariables(String val) {
// If it has no references or HTML entities then no work
// need to be done
if (val.indexOf('$') < 0 && val.indexOf('&') < 0) return val;
// HTML decoding
val = val.replace("&lt;", "<");
val = val.replace("&gt;", ">");
val = val.replace("&quot;", "\"");
val = val.replace("&amp;", "&");
StringBuilder sb = new StringBuilder(val);
int charStart = sb.indexOf("&#");
while (charStart > -1) {
int charEnd = sb.indexOf(";", charStart);
if (charEnd > -1) {
char c = (char) Integer.parseInt(
sb.substring(charStart + 2, charEnd));
sb.delete(charStart, charEnd + 1);
sb.insert(charStart, c);
charStart = sb.indexOf("&#");
} else {
break;
}
}
for (int i = 0; i < sb.length();) {
// Find the next $
for (; i < sb.length(); i++) {
if (sb.charAt(i) == '$') {
i++;
break;
}
}
if (i == sb.length()) break;
// Check to see if the $ is escaped
if (i > 1 && sb.charAt(i - 2) == '\\') {
sb.deleteCharAt(i - 2);
i--;
continue;
}
int nameStart = i;
int start = i - 1;
int end = -1;
int nameEnd = -1;
char endChar = ' ';
// Check for {} wrapped var
if (sb.charAt(i) == '{') {
nameStart++;
endChar = '}';
}
// Find the end of the var reference
for (; i < sb.length(); i++) {
if (sb.charAt(i) == endChar) break;
}
end = i;
nameEnd = end;
if (endChar == '}') end++;
// We should now have enough to extract the var name
String varName = sb.substring(nameStart, nameEnd);
String value = getVariableValue(varName);
if (value == null) value = "";
// Replace the var name with its value
sb.replace(start, end, value);
// Start searching for the next $ after the value
// that was just substituted.
i = start + value.length();
}
return sb.toString();
}
protected String formatDate(Date date, TimeZone timeZone) {
String retVal;
if (timeZone != null) {
//we temporarily change strftime. Since SSIMediator is inherently
// single-threaded, this
//isn't a problem
TimeZone oldTimeZone = strftime.getTimeZone();
strftime.setTimeZone(timeZone);
retVal = strftime.format(date);
strftime.setTimeZone(oldTimeZone);
} else {
retVal = strftime.format(date);
}
return retVal;
}
protected String encode(String value, String encoding) {
String retVal = null;
if (encoding.equalsIgnoreCase(ENCODING_URL)) {
retVal = URLEncoder.DEFAULT.encode(value, StandardCharsets.UTF_8);
} else if (encoding.equalsIgnoreCase(ENCODING_NONE)) {
retVal = value;
} else if (encoding.equalsIgnoreCase(ENCODING_ENTITY)) {
retVal = Escape.htmlElementContent(value);
} else {
//This shouldn't be possible
throw new IllegalArgumentException("Unknown encoding: " + encoding);
}
return retVal;
}
public void log(String message) {
ssiExternalResolver.log(message, null);
}
public void log(String message, Throwable throwable) {
ssiExternalResolver.log(message, throwable);
}
protected void setDateVariables(boolean fromConstructor) {
boolean alreadySet = ssiExternalResolver.getVariableValue(className
+ ".alreadyset") != null;
//skip this if we are being called from the constructor, and this has
// already
// been set
if (!(fromConstructor && alreadySet)) {
ssiExternalResolver.setVariableValue(className + ".alreadyset",
"true");
Date date = new Date();
TimeZone timeZone = TimeZone.getTimeZone("GMT");
String retVal = formatDate(date, timeZone);
//If we are setting on of the date variables, we want to remove
// them from the
// user
//defined list of variables, because this is what Apache does
setVariableValue("DATE_GMT", null);
ssiExternalResolver.setVariableValue(className + ".DATE_GMT",
retVal);
retVal = formatDate(date, null);
setVariableValue("DATE_LOCAL", null);
ssiExternalResolver.setVariableValue(className + ".DATE_LOCAL",
retVal);
retVal = formatDate(new Date(lastModifiedDate), null);
setVariableValue("LAST_MODIFIED", null);
ssiExternalResolver.setVariableValue(className + ".LAST_MODIFIED",
retVal);
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.PrintWriter;
import java.util.Collection;
/**
* Implements the Server-side #printenv command
*
* @author Dan Sandberg
* @author David Becker
*/
public class SSIPrintenv implements SSICommand {
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer) {
long lastModified = 0;
//any arguments should produce an error
if (paramNames.length > 0) {
String errorMessage = ssiMediator.getConfigErrMsg();
writer.write(errorMessage);
} else {
Collection<String> variableNames = ssiMediator.getVariableNames();
for (String variableName : variableNames) {
String variableValue = ssiMediator.getVariableValue(variableName, SSIMediator.ENCODING_ENTITY);
//This shouldn't happen, since all the variable names must
// have values
if (variableValue == null) {
variableValue = "(none)";
}
writer.write(variableName);
writer.write('=');
writer.write(variableValue);
writer.write('\n');
lastModified = System.currentTimeMillis();
}
}
return lastModified;
}
}

View File

@@ -0,0 +1,333 @@
/*
* 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.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Locale;
import java.util.StringTokenizer;
import org.apache.catalina.util.IOTools;
/**
* The entry point to SSI processing. This class does the actual parsing,
* delegating to the SSIMediator, SSICommand, and SSIExternalResolver as
* necessary[
*
* @author Dan Sandberg
* @author David Becker
*/
public class SSIProcessor {
/** The start pattern */
protected static final String COMMAND_START = "<!--#";
/** The end pattern */
protected static final String COMMAND_END = "-->";
protected final SSIExternalResolver ssiExternalResolver;
protected final HashMap<String,SSICommand> commands = new HashMap<>();
protected final int debug;
protected final boolean allowExec;
public SSIProcessor(SSIExternalResolver ssiExternalResolver, int debug,
boolean allowExec) {
this.ssiExternalResolver = ssiExternalResolver;
this.debug = debug;
this.allowExec = allowExec;
addBuiltinCommands();
}
protected void addBuiltinCommands() {
addCommand("config", new SSIConfig());
addCommand("echo", new SSIEcho());
if (allowExec) {
addCommand("exec", new SSIExec());
}
addCommand("include", new SSIInclude());
addCommand("flastmod", new SSIFlastmod());
addCommand("fsize", new SSIFsize());
addCommand("printenv", new SSIPrintenv());
addCommand("set", new SSISet());
SSIConditional ssiConditional = new SSIConditional();
addCommand("if", ssiConditional);
addCommand("elif", ssiConditional);
addCommand("endif", ssiConditional);
addCommand("else", ssiConditional);
}
public void addCommand(String name, SSICommand command) {
commands.put(name, command);
}
/**
* Process a file with server-side commands, reading from reader and
* writing the processed version to writer. NOTE: We really should be doing
* this in a streaming way rather than converting it to an array first.
*
* @param reader
* the reader to read the file containing SSIs from
* @param lastModifiedDate resource last modification date
* @param writer
* the writer to write the file with the SSIs processed.
* @return the most current modified date resulting from any SSI commands
* @throws IOException
* when things go horribly awry. Should be unlikely since the
* SSICommand usually catches 'normal' IOExceptions.
*/
public long process(Reader reader, long lastModifiedDate,
PrintWriter writer) throws IOException {
SSIMediator ssiMediator = new SSIMediator(ssiExternalResolver,
lastModifiedDate);
StringWriter stringWriter = new StringWriter();
IOTools.flow(reader, stringWriter);
String fileContents = stringWriter.toString();
stringWriter = null;
int index = 0;
boolean inside = false;
StringBuilder command = new StringBuilder();
try {
while (index < fileContents.length()) {
char c = fileContents.charAt(index);
if (!inside) {
if (c == COMMAND_START.charAt(0)
&& charCmp(fileContents, index, COMMAND_START)) {
inside = true;
index += COMMAND_START.length();
command.setLength(0); //clear the command string
} else {
if (!ssiMediator.getConditionalState().processConditionalCommandsOnly) {
writer.write(c);
}
index++;
}
} else {
if (c == COMMAND_END.charAt(0)
&& charCmp(fileContents, index, COMMAND_END)) {
inside = false;
index += COMMAND_END.length();
String strCmd = parseCmd(command);
if (debug > 0) {
ssiExternalResolver.log(
"SSIProcessor.process -- processing command: "
+ strCmd, null);
}
String[] paramNames = parseParamNames(command, strCmd
.length());
String[] paramValues = parseParamValues(command,
strCmd.length(), paramNames.length);
//We need to fetch this value each time, since it may
// change
// during the loop
String configErrMsg = ssiMediator.getConfigErrMsg();
SSICommand ssiCommand =
commands.get(strCmd.toLowerCase(Locale.ENGLISH));
String errorMessage = null;
if (ssiCommand == null) {
errorMessage = "Unknown command: " + strCmd;
} else if (paramValues == null) {
errorMessage = "Error parsing directive parameters.";
} else if (paramNames.length != paramValues.length) {
errorMessage = "Parameter names count does not match parameter values count on command: "
+ strCmd;
} else {
// don't process the command if we are processing
// conditional
// commands only and the
// command is not conditional
if (!ssiMediator.getConditionalState().processConditionalCommandsOnly
|| ssiCommand instanceof SSIConditional) {
long lmd = ssiCommand.process(ssiMediator, strCmd,
paramNames, paramValues, writer);
if (lmd > lastModifiedDate) {
lastModifiedDate = lmd;
}
}
}
if (errorMessage != null) {
ssiExternalResolver.log(errorMessage, null);
writer.write(configErrMsg);
}
} else {
command.append(c);
index++;
}
}
}
} catch (SSIStopProcessingException e) {
//If we are here, then we have already stopped processing, so all
// is good
}
return lastModifiedDate;
}
/**
* Parse a StringBuilder and take out the param type token. Called from
* <code>requestHandler</code>
*
* @param cmd
* a value of type 'StringBuilder'
* @param start index on which parsing will start
* @return an array with the parameter names
*/
protected String[] parseParamNames(StringBuilder cmd, int start) {
int bIdx = start;
int i = 0;
int quotes = 0;
boolean inside = false;
StringBuilder retBuf = new StringBuilder();
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;
boolean escaped = false;
for (; bIdx < cmd.length() && quotes != 2; bIdx++) {
char c = cmd.charAt(bIdx);
// Need to skip escaped characters
if (c == '\\' && !escaped) {
escaped = true;
continue;
}
if (c == '"' && !escaped) quotes++;
escaped = false;
}
}
}
StringTokenizer str = new StringTokenizer(retBuf.toString(), "=");
String[] retString = new String[str.countTokens()];
while (str.hasMoreTokens()) {
retString[i++] = str.nextToken().trim();
}
return retString;
}
/**
* Parse a StringBuilder and take out the param token. Called from
* <code>requestHandler</code>
*
* @param cmd
* a value of type 'StringBuilder'
* @param start index on which parsing will start
* @param count number of values which should be parsed
* @return an array with the parameter values
*/
protected String[] parseParamValues(StringBuilder cmd, int start, int count) {
int valIndex = 0;
boolean inside = false;
String[] vals = new String[count];
StringBuilder sb = new StringBuilder();
char endQuote = 0;
for (int bIdx = start; bIdx < cmd.length(); bIdx++) {
if (!inside) {
while (bIdx < cmd.length() && !isQuote(cmd.charAt(bIdx)))
bIdx++;
if (bIdx >= cmd.length()) break;
inside = !inside;
endQuote = cmd.charAt(bIdx);
} else {
boolean escaped = false;
for (; bIdx < cmd.length(); bIdx++) {
char c = cmd.charAt(bIdx);
// Check for escapes
if (c == '\\' && !escaped) {
escaped = true;
continue;
}
// If we reach the other " then stop
if (c == endQuote && !escaped) break;
// Since parsing of attributes and var
// substitution is done in separate places,
// we need to leave escape in the string
if (c == '$' && escaped) sb.append('\\');
escaped = false;
sb.append(c);
}
// If we hit the end without seeing a quote
// the signal an error
if (bIdx == cmd.length()) return null;
vals[valIndex++] = sb.toString();
sb.delete(0, sb.length()); // clear the buffer
inside = !inside;
}
}
return vals;
}
/**
* Parse a StringBuilder and take out the command token. Called from
* <code>requestHandler</code>
*
* @param cmd
* a value of type 'StringBuilder'
* @return a value of type 'String', or null if there is none
*/
private String parseCmd(StringBuilder cmd) {
int firstLetter = -1;
int lastLetter = -1;
for (int i = 0; i < cmd.length(); i++) {
char c = cmd.charAt(i);
if (Character.isLetter(c)) {
if (firstLetter == -1) {
firstLetter = i;
}
lastLetter = i;
} else if (isSpace(c)) {
if (lastLetter > -1) {
break;
}
} else {
break;
}
}
if (firstLetter == -1) {
return "";
} else {
return cmd.substring(firstLetter, lastLetter + 1);
}
}
protected boolean charCmp(String buf, int index, String command) {
return buf.regionMatches(index, command, 0, command.length());
}
protected boolean isSpace(char c) {
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
}
protected boolean isQuote(char c) {
return c == '\'' || c == '\"' || c == '`';
}
}

View File

@@ -0,0 +1,226 @@
/*
* 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.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet to process SSI requests within a webpage. Mapped to a path from
* within web.xml.
*
* @author Bip Thelin
* @author Amy Roh
* @author Dan Sandberg
* @author David Becker
*/
public class SSIServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/** Debug level for this servlet. */
protected int debug = 0;
/** Should the output be buffered. */
protected boolean buffered = false;
/** Expiration time in seconds for the doc. */
protected Long expires = null;
/** virtual path can be webapp-relative */
protected boolean isVirtualWebappRelative = false;
/** Input encoding. If not specified, uses platform default */
protected String inputEncoding = null;
/** Output encoding. If not specified, uses platform default */
protected String outputEncoding = "UTF-8";
/** Allow exec (normally blocked for security) */
protected boolean allowExec = false;
//----------------- Public methods.
/**
* Initialize this servlet.
*
* @exception ServletException
* if an error occurs
*/
@Override
public void init() throws ServletException {
if (getServletConfig().getInitParameter("debug") != null)
debug = Integer.parseInt(getServletConfig().getInitParameter("debug"));
isVirtualWebappRelative =
Boolean.parseBoolean(getServletConfig().getInitParameter("isVirtualWebappRelative"));
if (getServletConfig().getInitParameter("expires") != null)
expires = Long.valueOf(getServletConfig().getInitParameter("expires"));
buffered = Boolean.parseBoolean(getServletConfig().getInitParameter("buffered"));
inputEncoding = getServletConfig().getInitParameter("inputEncoding");
if (getServletConfig().getInitParameter("outputEncoding") != null)
outputEncoding = getServletConfig().getInitParameter("outputEncoding");
allowExec = Boolean.parseBoolean(
getServletConfig().getInitParameter("allowExec"));
if (debug > 0)
log("SSIServlet.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
*/
@Override
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
if (debug > 0) log("SSIServlet.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
*/
@Override
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
if (debug > 0) log("SSIServlet.doPost()");
requestHandler(req, res);
}
/**
* Process our request and locate right SSI command.
*
* @param req
* a value of type 'HttpServletRequest'
* @param res
* a value of type 'HttpServletResponse'
* @throws IOException an IO error occurred
*/
protected void requestHandler(HttpServletRequest req,
HttpServletResponse res) throws IOException {
ServletContext servletContext = getServletContext();
String path = SSIServletRequestUtil.getRelativePath(req);
if (debug > 0)
log("SSIServlet.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(Locale.ENGLISH).startsWith("/WEB-INF")
|| path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) {
res.sendError(HttpServletResponse.SC_NOT_FOUND, path);
log("Can't serve file: " + path);
return;
}
URL resource = servletContext.getResource(path);
if (resource == null) {
res.sendError(HttpServletResponse.SC_NOT_FOUND, path);
log("Can't find file: " + path);
return;
}
String resourceMimeType = servletContext.getMimeType(path);
if (resourceMimeType == null) {
resourceMimeType = "text/html";
}
res.setContentType(resourceMimeType + ";charset=" + outputEncoding);
if (expires != null) {
res.setDateHeader("Expires", (new java.util.Date()).getTime()
+ expires.longValue() * 1000);
}
processSSI(req, res, resource);
}
protected void processSSI(HttpServletRequest req, HttpServletResponse res,
URL resource) throws IOException {
SSIExternalResolver ssiExternalResolver =
new SSIServletExternalResolver(getServletContext(), req, res,
isVirtualWebappRelative, debug, inputEncoding);
SSIProcessor ssiProcessor = new SSIProcessor(ssiExternalResolver,
debug, allowExec);
PrintWriter printWriter = null;
StringWriter stringWriter = null;
if (buffered) {
stringWriter = new StringWriter();
printWriter = new PrintWriter(stringWriter);
} else {
printWriter = res.getWriter();
}
URLConnection resourceInfo = resource.openConnection();
InputStream resourceInputStream = resourceInfo.getInputStream();
String encoding = resourceInfo.getContentEncoding();
if (encoding == null) {
encoding = inputEncoding;
}
InputStreamReader isr;
if (encoding == null) {
isr = new InputStreamReader(resourceInputStream);
} else {
isr = new InputStreamReader(resourceInputStream, encoding);
}
BufferedReader bufferedReader = new BufferedReader(isr);
long lastModified = ssiProcessor.process(bufferedReader,
resourceInfo.getLastModified(), printWriter);
if (lastModified > 0) {
res.setDateHeader("last-modified", lastModified);
}
if (buffered) {
printWriter.flush();
@SuppressWarnings("null")
String text = stringWriter.toString();
res.getWriter().write(text);
}
bufferedReader.close();
}
}

View File

@@ -0,0 +1,582 @@
/*
* 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.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.coyote.Constants;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.buf.UDecoder;
import org.apache.tomcat.util.http.RequestUtil;
/**
* An implementation of SSIExternalResolver that is used with servlets.
*
* @author Dan Sandberg
* @author David Becker
*/
public class SSIServletExternalResolver implements SSIExternalResolver {
protected final String VARIABLE_NAMES[] = {"AUTH_TYPE", "CONTENT_LENGTH",
"CONTENT_TYPE", "DOCUMENT_NAME", "DOCUMENT_URI",
"GATEWAY_INTERFACE", "HTTP_ACCEPT", "HTTP_ACCEPT_ENCODING",
"HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION", "HTTP_HOST",
"HTTP_REFERER", "HTTP_USER_AGENT", "PATH_INFO", "PATH_TRANSLATED",
"QUERY_STRING", "QUERY_STRING_UNESCAPED", "REMOTE_ADDR",
"REMOTE_HOST", "REMOTE_PORT", "REMOTE_USER", "REQUEST_METHOD",
"REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_ADDR",
"SERVER_NAME", "SERVER_PORT", "SERVER_PROTOCOL", "SERVER_SOFTWARE",
"UNIQUE_ID"};
protected final ServletContext context;
protected final HttpServletRequest req;
protected final HttpServletResponse res;
protected final boolean isVirtualWebappRelative;
protected final int debug;
protected final String inputEncoding;
public SSIServletExternalResolver(ServletContext context,
HttpServletRequest req, HttpServletResponse res,
boolean isVirtualWebappRelative, int debug, String inputEncoding) {
this.context = context;
this.req = req;
this.res = res;
this.isVirtualWebappRelative = isVirtualWebappRelative;
this.debug = debug;
this.inputEncoding = inputEncoding;
}
@Override
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) {
context.log(message, throwable);
} else {
context.log(message);
}
}
@Override
public void addVariableNames(Collection<String> 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<String> e = req.getAttributeNames();
while (e.hasMoreElements()) {
String name = 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<String> e = req.getAttributeNames();
while (e.hasMoreElements()) {
String name = 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.");
}
@Override
public void setVariableValue(String name, String value) {
if (!isNameReserved(name)) {
req.setAttribute(name, value);
}
}
@Override
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;
String[] nameParts = name.toUpperCase(Locale.ENGLISH).split("_");
int requiredParts = 2;
if (nameParts.length == 1) {
if (nameParts[0].equals("PATH")) {
requiredParts = 1;
}
}
else if (nameParts[0].equals("AUTH")) {
if (nameParts[1].equals("TYPE")) {
retVal = req.getAuthType();
}
} else if(nameParts[0].equals("CONTENT")) {
if (nameParts[1].equals("LENGTH")) {
long contentLength = req.getContentLengthLong();
if (contentLength >= 0) {
retVal = Long.toString(contentLength);
}
} else if (nameParts[1].equals("TYPE")) {
retVal = req.getContentType();
}
} else if (nameParts[0].equals("DOCUMENT")) {
if (nameParts[1].equals("NAME")) {
String requestURI = req.getRequestURI();
retVal = requestURI.substring(requestURI.lastIndexOf('/') + 1);
} else if (nameParts[1].equals("URI")) {
retVal = req.getRequestURI();
}
} else if (name.equalsIgnoreCase("GATEWAY_INTERFACE")) {
retVal = "CGI/1.1";
} else if (nameParts[0].equals("HTTP")) {
if (nameParts[1].equals("ACCEPT")) {
String accept = null;
if (nameParts.length == 2) {
accept = "Accept";
} else if (nameParts[2].equals("ENCODING")) {
requiredParts = 3;
accept = "Accept-Encoding";
} else if (nameParts[2].equals("LANGUAGE")) {
requiredParts = 3;
accept = "Accept-Language";
}
if (accept != null) {
Enumeration<String> acceptHeaders = req.getHeaders(accept);
if (acceptHeaders != null)
if (acceptHeaders.hasMoreElements()) {
StringBuilder rv = new StringBuilder(
acceptHeaders.nextElement());
while (acceptHeaders.hasMoreElements()) {
rv.append(", ");
rv.append(acceptHeaders.nextElement());
}
retVal = rv.toString();
}
}
}
else if (nameParts[1].equals("CONNECTION")) {
retVal = req.getHeader("Connection");
}
else if (nameParts[1].equals("HOST")) {
retVal = req.getHeader("Host");
}
else if (nameParts[1].equals("REFERER")) {
retVal = req.getHeader("Referer");
}
else if (nameParts[1].equals("USER"))
if (nameParts.length == 3)
if (nameParts[2].equals("AGENT")) {
requiredParts = 3;
retVal = req.getHeader("User-Agent");
}
} else if (nameParts[0].equals("PATH")) {
if (nameParts[1].equals("INFO")) {
retVal = req.getPathInfo();
} else if (nameParts[1].equals("TRANSLATED")) {
retVal = req.getPathTranslated();
}
} else if (nameParts[0].equals("QUERY")) {
if (nameParts[1].equals("STRING")) {
String queryString = req.getQueryString();
if (nameParts.length == 2) {
//apache displays this as an empty string rather than (none)
retVal = nullToEmptyString(queryString);
} else if (nameParts[2].equals("UNESCAPED")) {
requiredParts = 3;
if (queryString != null) {
Charset uriCharset = null;
Charset requestCharset = null;
boolean useBodyEncodingForURI = false;
// Get encoding settings from request / connector if
// possible
if (req instanceof Request) {
try {
requestCharset = ((Request)req).getCoyoteRequest().getCharset();
} catch (UnsupportedEncodingException e) {
// Ignore
}
Connector connector = ((Request)req).getConnector();
uriCharset = connector.getURICharset();
useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
}
Charset queryStringCharset;
// If valid, apply settings from request / connector
if (useBodyEncodingForURI && requestCharset != null) {
queryStringCharset = requestCharset;
} else if (uriCharset != null) {
queryStringCharset = uriCharset;
} else {
// Use default as a last resort
queryStringCharset = Constants.DEFAULT_URI_CHARSET;
}
retVal = UDecoder.URLDecode(queryString, queryStringCharset);
}
}
}
} else if(nameParts[0].equals("REMOTE")) {
if (nameParts[1].equals("ADDR")) {
retVal = req.getRemoteAddr();
} else if (nameParts[1].equals("HOST")) {
retVal = req.getRemoteHost();
} else if (nameParts[1].equals("IDENT")) {
// Not implemented
} else if (nameParts[1].equals("PORT")) {
retVal = Integer.toString( req.getRemotePort());
} else if (nameParts[1].equals("USER")) {
retVal = req.getRemoteUser();
}
} else if(nameParts[0].equals("REQUEST")) {
if (nameParts[1].equals("METHOD")) {
retVal = req.getMethod();
}
else if (nameParts[1].equals("URI")) {
// If this is an error page, get the original URI
retVal = (String) req.getAttribute(
RequestDispatcher.FORWARD_REQUEST_URI);
if (retVal == null) retVal=req.getRequestURI();
}
} else if (nameParts[0].equals("SCRIPT")) {
String scriptName = req.getServletPath();
if (nameParts[1].equals("FILENAME")) {
retVal = context.getRealPath(scriptName);
}
else if (nameParts[1].equals("NAME")) {
retVal = scriptName;
}
} else if (nameParts[0].equals("SERVER")) {
if (nameParts[1].equals("ADDR")) {
retVal = req.getLocalAddr();
}
if (nameParts[1].equals("NAME")) {
retVal = req.getServerName();
} else if (nameParts[1].equals("PORT")) {
retVal = Integer.toString(req.getServerPort());
} else if (nameParts[1].equals("PROTOCOL")) {
retVal = req.getProtocol();
} else if (nameParts[1].equals("SOFTWARE")) {
StringBuilder rv = new StringBuilder(context.getServerInfo());
rv.append(" ");
rv.append(System.getProperty("java.vm.name"));
rv.append("/");
rv.append(System.getProperty("java.vm.version"));
rv.append(" ");
rv.append(System.getProperty("os.name"));
retVal = rv.toString();
}
} else if (name.equalsIgnoreCase("UNIQUE_ID")) {
retVal = req.getRequestedSessionId();
}
if (requiredParts != nameParts.length) return null;
return retVal;
}
@Override
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 name
retVal = servletPath.substring(0, lastSlash + 1);
}
return retVal;
}
protected String getPathWithoutContext(final String contextPath,
final String servletPath) {
if (servletPath.startsWith(contextPath)) {
return servletPath.substring(contextPath.length());
}
return servletPath;
}
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 = RequestUtil.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);
ServletContextAndPath csAndP = new ServletContextAndPath(
context, path);
return csAndP;
}
protected ServletContextAndPath getServletContextAndPathFromVirtualPath(
String virtualPath) throws IOException {
if (!virtualPath.startsWith("/") && !virtualPath.startsWith("\\")) {
return new ServletContextAndPath(context,
getAbsolutePath(virtualPath));
}
String normalized = RequestUtil.normalize(virtualPath);
if (isVirtualWebappRelative) {
return new ServletContextAndPath(context, normalized);
}
ServletContext normContext = context.getContext(normalized);
if (normContext == 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(normContext)) {
String noContext = getPathWithoutContext(
normContext.getContextPath(), normalized);
if (noContext == null) {
throw new IOException(
"Couldn't remove context from path: "
+ normalized);
}
return new ServletContextAndPath(normContext, noContext);
}
return new ServletContextAndPath(normContext, normalized);
}
//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;
}
@Override
public long getFileLastModified(String path, boolean virtual)
throws IOException {
long lastModified = 0;
try {
URLConnection urlConnection = getURLConnection(path, virtual);
lastModified = urlConnection.getLastModified();
} catch (IOException e) {
// Ignore this. It will always fail for non-file based includes
}
return lastModified;
}
@Override
public long getFileSize(String path, boolean virtual) throws IOException {
long fileSize = -1;
try {
URLConnection urlConnection = getURLConnection(path, virtual);
fileSize = urlConnection.getContentLengthLong();
} catch (IOException e) {
// Ignore this. It will always fail for non-file based includes
}
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.
@Override
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 platform default encoding unless otherwise specified
String retVal;
if (inputEncoding == null) {
retVal = new String( bytes );
} else {
retVal = new String (bytes,
B2CConverter.getCharset(inputEncoding));
}
//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("") && !req.getMethod().equalsIgnoreCase("HEAD")) {
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 static class ServletContextAndPath {
protected final ServletContext servletContext;
protected final String path;
public ServletContextAndPath(ServletContext servletContext,
String path) {
this.servletContext = servletContext;
this.path = path;
}
public ServletContext getServletContext() {
return servletContext;
}
public String getPath() {
return path;
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import org.apache.tomcat.util.http.RequestUtil;
public class SSIServletRequestUtil {
/**
* Return the relative path associated with this servlet. Taken from
* DefaultServlet.java. Perhaps this should be put in
* org.apache.catalina.util somewhere? Seems like it would be widely used.
*
* @param request
* The servlet request we are processing
* @return the relative path
*/
public static String getRelativePath(HttpServletRequest request) {
// Are we being processed by a RequestDispatcher.include()?
if (request.getAttribute(
RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
String result = (String)request.getAttribute(
RequestDispatcher.INCLUDE_PATH_INFO);
if (result == null)
result = (String)request.getAttribute(
RequestDispatcher.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 RequestUtil.normalize(result);
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.PrintWriter;
/**
* Implements the Server-side #set command
*
* @author Paul Speed
* @author Dan Sandberg
* @author David Becker
*/
public class SSISet implements SSICommand {
/**
* @see SSICommand
*/
@Override
public long process(SSIMediator ssiMediator, String commandName,
String[] paramNames, String[] paramValues, PrintWriter writer)
throws SSIStopProcessingException {
long lastModified = 0;
String errorMessage = ssiMediator.getConfigErrMsg();
String variableName = null;
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
String paramValue = paramValues[i];
if (paramName.equalsIgnoreCase("var")) {
variableName = paramValue;
} else if (paramName.equalsIgnoreCase("value")) {
if (variableName != null) {
String substitutedValue = ssiMediator
.substituteVariables(paramValue);
ssiMediator.setVariableValue(variableName,
substitutedValue);
lastModified = System.currentTimeMillis();
} else {
ssiMediator.log("#set--no variable specified");
writer.write(errorMessage);
throw new SSIStopProcessingException();
}
} else {
ssiMediator.log("#set--Invalid attribute: " + paramName);
writer.write(errorMessage);
throw new SSIStopProcessingException();
}
}
return lastModified;
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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;
/**
* Exception used to tell SSIProcessor that it should stop processing SSI
* commands. This is used to mimic the Apache behavior in #set with invalid
* attributes.
*
* @author Paul Speed
* @author Dan Sandberg
*/
public class SSIStopProcessingException extends Exception {
private static final long serialVersionUID = 1L;
// No specific functionality for this class
}

View File

@@ -0,0 +1,32 @@
<!--
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.
-->
<body>
<p>This package contains code that is used by the SsiInvoker.</p>
<p>This class consists of <code>SsiMediator.java</code> which works as a
mediator between the different SsiCommands. To add a command you have to
implement the <code>SsiCommand</code> interface and extend the
<code>SsiMediator</code>. Commands currently implemented are</p>
<ul>
<li><b>SsiConfig</b> - Implementation of the NCSA command Config i.e. &lt;!--#config errmsg="error?"--&gt;</li>
<li><b>SsiEcho</b> - Implementation of the NCSA command Echo i.e. &lt;!--#echo var="SERVER_NAME"--&gt;</li>
<li><b>SsiExec</b> - Not implemented</li>
<li><b>SsiFlastMod</b> - Implementation of the NCSA command flastmod i.e. &lt;!--#flastmod virtual="file"--&gt;</li>
<li><b>SsiFsize</b> - Implementation of the NCSA command fsize i.e. &lt;!--#fsize file="file"--&gt;</li>
<li><b>SsiInclude</b> - Implementation of the NCSA command Include i.e. &lt;!--#config virtual="includefile"--&gt;</li>
</ul>
</body>