init
This commit is contained in:
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 == '`';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user