init
This commit is contained in:
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
442
java/org/apache/catalina/ssi/ExpressionParseTree.java
Normal file
442
java/org/apache/catalina/ssi/ExpressionParseTree.java
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
186
java/org/apache/catalina/ssi/ExpressionTokenizer.java
Normal file
186
java/org/apache/catalina/ssi/ExpressionTokenizer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
195
java/org/apache/catalina/ssi/ResponseIncludeWrapper.java
Normal file
195
java/org/apache/catalina/ssi/ResponseIncludeWrapper.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
java/org/apache/catalina/ssi/SSICommand.java
Normal file
50
java/org/apache/catalina/ssi/SSICommand.java
Normal 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;
|
||||
}
|
||||
139
java/org/apache/catalina/ssi/SSIConditional.java
Normal file
139
java/org/apache/catalina/ssi/SSIConditional.java
Normal 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;
|
||||
}
|
||||
}
|
||||
43
java/org/apache/catalina/ssi/SSIConditionalState.java
Normal file
43
java/org/apache/catalina/ssi/SSIConditionalState.java
Normal 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;
|
||||
}
|
||||
59
java/org/apache/catalina/ssi/SSIConfig.java
Normal file
59
java/org/apache/catalina/ssi/SSIConfig.java
Normal 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;
|
||||
}
|
||||
}
|
||||
74
java/org/apache/catalina/ssi/SSIEcho.java
Normal file
74
java/org/apache/catalina/ssi/SSIEcho.java
Normal 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);
|
||||
}
|
||||
}
|
||||
83
java/org/apache/catalina/ssi/SSIExec.java
Normal file
83
java/org/apache/catalina/ssi/SSIExec.java
Normal 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;
|
||||
}
|
||||
}
|
||||
76
java/org/apache/catalina/ssi/SSIExternalResolver.java
Normal file
76
java/org/apache/catalina/ssi/SSIExternalResolver.java
Normal 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);
|
||||
}
|
||||
175
java/org/apache/catalina/ssi/SSIFilter.java
Normal file
175
java/org/apache/catalina/ssi/SSIFilter.java
Normal 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
|
||||
}
|
||||
}
|
||||
77
java/org/apache/catalina/ssi/SSIFlastmod.java
Normal file
77
java/org/apache/catalina/ssi/SSIFlastmod.java
Normal 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);
|
||||
}
|
||||
}
|
||||
124
java/org/apache/catalina/ssi/SSIFsize.java
Normal file
124
java/org/apache/catalina/ssi/SSIFsize.java
Normal 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;
|
||||
}
|
||||
}
|
||||
66
java/org/apache/catalina/ssi/SSIInclude.java
Normal file
66
java/org/apache/catalina/ssi/SSIInclude.java
Normal 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;
|
||||
}
|
||||
}
|
||||
340
java/org/apache/catalina/ssi/SSIMediator.java
Normal file
340
java/org/apache/catalina/ssi/SSIMediator.java
Normal 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("<", "<");
|
||||
val = val.replace(">", ">");
|
||||
val = val.replace(""", "\"");
|
||||
val = val.replace("&", "&");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
java/org/apache/catalina/ssi/SSIPrintenv.java
Normal file
59
java/org/apache/catalina/ssi/SSIPrintenv.java
Normal 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;
|
||||
}
|
||||
}
|
||||
333
java/org/apache/catalina/ssi/SSIProcessor.java
Normal file
333
java/org/apache/catalina/ssi/SSIProcessor.java
Normal 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 == '`';
|
||||
}
|
||||
}
|
||||
226
java/org/apache/catalina/ssi/SSIServlet.java
Normal file
226
java/org/apache/catalina/ssi/SSIServlet.java
Normal 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();
|
||||
}
|
||||
}
|
||||
582
java/org/apache/catalina/ssi/SSIServletExternalResolver.java
Normal file
582
java/org/apache/catalina/ssi/SSIServletExternalResolver.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
java/org/apache/catalina/ssi/SSIServletRequestUtil.java
Normal file
57
java/org/apache/catalina/ssi/SSIServletRequestUtil.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
64
java/org/apache/catalina/ssi/SSISet.java
Normal file
64
java/org/apache/catalina/ssi/SSISet.java
Normal 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;
|
||||
}
|
||||
}
|
||||
32
java/org/apache/catalina/ssi/SSIStopProcessingException.java
Normal file
32
java/org/apache/catalina/ssi/SSIStopProcessingException.java
Normal 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
|
||||
}
|
||||
32
java/org/apache/catalina/ssi/package.html
Normal file
32
java/org/apache/catalina/ssi/package.html
Normal 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. <!--#config errmsg="error?"--></li>
|
||||
<li><b>SsiEcho</b> - Implementation of the NCSA command Echo i.e. <!--#echo var="SERVER_NAME"--></li>
|
||||
<li><b>SsiExec</b> - Not implemented</li>
|
||||
<li><b>SsiFlastMod</b> - Implementation of the NCSA command flastmod i.e. <!--#flastmod virtual="file"--></li>
|
||||
<li><b>SsiFsize</b> - Implementation of the NCSA command fsize i.e. <!--#fsize file="file"--></li>
|
||||
<li><b>SsiInclude</b> - Implementation of the NCSA command Include i.e. <!--#config virtual="includefile"--></li>
|
||||
</ul>
|
||||
</body>
|
||||
Reference in New Issue
Block a user