convert SsiProcessor to kotlin

This commit is contained in:
Vladimir Krivosheev
2015-10-28 13:45:55 +01:00
parent 99d2b417f1
commit 8a7a4bdf17
@@ -13,429 +13,419 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.builtInWebServer.ssi;
package org.jetbrains.builtInWebServer.ssi
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.SmartList;
import gnu.trove.THashMap;
import io.netty.buffer.ByteBufUtf8Writer;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.diagnostic.Logger
import com.intellij.util.SmartList
import gnu.trove.THashMap
import io.netty.buffer.ByteBufUtf8Writer
import java.io.FileInputStream
import java.io.IOException
import java.util.*
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
internal val LOG = Logger.getInstance(SsiProcessor::class.java)
public class SsiProcessor {
static final Logger LOG = Logger.getInstance(SsiProcessor.class);
internal val COMMAND_START = "<!--#"
internal val COMMAND_END = "-->"
protected static final String COMMAND_START = "<!--#";
protected static final String COMMAND_END = "-->";
interface SsiCommand {
fun process(state: SsiProcessingState, commandName: String, paramNames: List<String>, paramValues: Array<String>, writer: ByteBufUtf8Writer): Long
}
protected final Map<String, SsiCommand> commands = new THashMap<String, SsiCommand>();
class SsiProcessor(allowExec: Boolean) {
private val commands: MutableMap<String, SsiCommand> = THashMap()
@SuppressWarnings("SpellCheckingInspection")
public SsiProcessor(boolean allowExec) {
commands.put("config", new SsiCommand() {
@Override
public long process(@NotNull SsiProcessingState state, @NotNull String commandName, @NotNull List<String> paramNames, @NotNull String[] paramValues, @NotNull ByteBufUtf8Writer writer) {
for (int i = 0; i < paramNames.size(); i++) {
String paramName = paramNames.get(i);
String paramValue = paramValues[i];
String substitutedValue = state.substituteVariables(paramValue);
if (paramName.equalsIgnoreCase("errmsg")) {
state.configErrorMessage = substitutedValue;
init {
commands.put("config", object : SsiCommand {
override fun process(state: SsiProcessingState, commandName: String, paramNames: List<String>, paramValues: Array<String>, writer: ByteBufUtf8Writer): Long {
for (i in paramNames.indices) {
val paramName = paramNames[i]
val paramValue = paramValues[i]
val substitutedValue = state.substituteVariables(paramValue)
if (paramName.equals("errmsg", ignoreCase = true)) {
state.configErrorMessage = substitutedValue
}
else if (paramName.equalsIgnoreCase("sizefmt")) {
state.configSizeFmt = substitutedValue;
else if (paramName.equals("sizefmt", ignoreCase = true)) {
state.configSizeFmt = substitutedValue
}
else if (paramName.equalsIgnoreCase("timefmt")) {
state.setConfigTimeFormat(substitutedValue, false);
else if (paramName.equals("timefmt", ignoreCase = true)) {
state.setConfigTimeFormat(substitutedValue, false)
}
else {
LOG.info("#config--Invalid attribute: " + paramName);
LOG.info("#config--Invalid attribute: " + paramName)
// We need to fetch this value each time, since it may change during the loop
writer.write(state.configErrorMessage);
writer.write(state.configErrorMessage)
}
}
return 0;
return 0
}
});
commands.put("echo", new SsiCommand() {
@Override
public long process(@NotNull SsiProcessingState state, @NotNull String commandName, @NotNull List<String> paramNames, @NotNull String[] paramValues, @NotNull ByteBufUtf8Writer writer) {
String encoding = "entity";
String originalValue = null;
String errorMessage = state.configErrorMessage;
for (int i = 0; i < paramNames.size(); i++) {
String paramName = paramNames.get(i);
String paramValue = paramValues[i];
if (paramName.equalsIgnoreCase("var")) {
originalValue = paramValue;
})
commands.put("echo", object : SsiCommand {
override fun process(state: SsiProcessingState, commandName: String, paramNames: List<String>, paramValues: Array<String>, writer: ByteBufUtf8Writer): Long {
var encoding = "entity"
var originalValue: String? = null
val errorMessage = state.configErrorMessage
for (i in paramNames.indices) {
val paramName = paramNames[i]
val paramValue = paramValues[i]
if (paramName.equals("var", ignoreCase = true)) {
originalValue = paramValue
}
else if (paramName.equalsIgnoreCase("encoding")) {
if (paramValue.equalsIgnoreCase("url") || paramValue.equalsIgnoreCase("entity") || paramValue.equalsIgnoreCase("none")) {
encoding = paramValue;
else if (paramName.equals("encoding", ignoreCase = true)) {
if (paramValue.equals("url", ignoreCase = true) || paramValue.equals("entity", ignoreCase = true) || paramValue.equals("none", ignoreCase = true)) {
encoding = paramValue
}
else {
LOG.info("#echo--Invalid encoding: " + paramValue);
writer.write(errorMessage);
LOG.info("#echo--Invalid encoding: " + paramValue)
writer.write(errorMessage)
}
}
else {
LOG.info("#echo--Invalid attribute: " + paramName);
writer.write(errorMessage);
LOG.info("#echo--Invalid attribute: " + paramName)
writer.write(errorMessage)
}
}
assert originalValue != null;
String variableValue = state.getVariableValue(originalValue, encoding);
writer.write(variableValue == null ? "(none)" : variableValue);
return System.currentTimeMillis();
val variableValue = state.getVariableValue(originalValue!!, encoding)
writer.write(variableValue ?: "(none)")
return System.currentTimeMillis()
}
});
})
//noinspection StatementWithEmptyBody
if (allowExec) {
// commands.put("exec", new SsiExec());
}
commands.put("include", new SsiCommand() {
@Override
public long process(@NotNull SsiProcessingState state, @NotNull String commandName, @NotNull List<String> paramNames, @NotNull String[] paramValues, @NotNull ByteBufUtf8Writer writer) {
long lastModified = 0;
String configErrorMessage = state.configErrorMessage;
for (int i = 0; i < paramNames.size(); i++) {
String paramName = paramNames.get(i);
if (paramName.equalsIgnoreCase("file") || paramName.equalsIgnoreCase("virtual")) {
String substitutedValue = state.substituteVariables(paramValues[i]);
commands.put("include", object : SsiCommand {
override fun process(state: SsiProcessingState, commandName: String, paramNames: List<String>, paramValues: Array<String>, writer: ByteBufUtf8Writer): Long {
var lastModified: Long = 0
val configErrorMessage = state.configErrorMessage
for (i in paramNames.indices) {
val paramName = paramNames[i]
if (paramName.equals("file", ignoreCase = true) || paramName.equals("virtual", ignoreCase = true)) {
val substitutedValue = state.substituteVariables(paramValues[i])
try {
boolean virtual = paramName.equalsIgnoreCase("virtual");
lastModified = state.ssiExternalResolver.getFileLastModified(substitutedValue, virtual);
File file = state.ssiExternalResolver.findFile(substitutedValue, virtual);
val virtual = paramName.equals("virtual", ignoreCase = true)
lastModified = state.ssiExternalResolver.getFileLastModified(substitutedValue, virtual)
val file = state.ssiExternalResolver.findFile(substitutedValue, virtual)
if (file == null) {
LOG.warn("#include-- Couldn't find file: " + substitutedValue);
return 0;
LOG.warn("#include-- Couldn't find file: " + substitutedValue)
return 0
}
InputStream in = new FileInputStream(file);
val `in` = FileInputStream(file)
try {
writer.write(in, (int)file.length());
writer.write(`in`, file.length().toInt())
}
finally {
in.close();
`in`.close()
}
}
catch (IOException e) {
LOG.warn("#include--Couldn't include file: " + substitutedValue, e);
writer.write(configErrorMessage);
catch (e: IOException) {
LOG.warn("#include--Couldn't include file: " + substitutedValue, e)
writer.write(configErrorMessage)
}
}
else {
LOG.info("#include--Invalid attribute: " + paramName);
writer.write(configErrorMessage);
LOG.info("#include--Invalid attribute: " + paramName)
writer.write(configErrorMessage)
}
}
return lastModified;
return lastModified
}
});
commands.put("flastmod", new SsiCommand() {
@Override
public long process(@NotNull SsiProcessingState state, @NotNull String commandName, @NotNull List<String> paramNames, @NotNull String[] paramValues, @NotNull ByteBufUtf8Writer writer) {
long lastModified = 0;
String configErrMsg = state.configErrorMessage;
for (int i = 0; i < paramNames.size(); i++) {
String paramName = paramNames.get(i);
String paramValue = paramValues[i];
String substitutedValue = state.substituteVariables(paramValue);
if (paramName.equalsIgnoreCase("file") || paramName.equalsIgnoreCase("virtual")) {
boolean virtual = paramName.equalsIgnoreCase("virtual");
lastModified = state.ssiExternalResolver.getFileLastModified(substitutedValue, virtual);
Strftime strftime = new Strftime(state.configTimeFmt, Locale.US);
writer.write(strftime.format(new Date(lastModified)));
})
commands.put("flastmod", object : SsiCommand {
override fun process(state: SsiProcessingState, commandName: String, paramNames: List<String>, paramValues: Array<String>, writer: ByteBufUtf8Writer): Long {
var lastModified: Long = 0
val configErrMsg = state.configErrorMessage
for (i in paramNames.indices) {
val paramName = paramNames[i]
val paramValue = paramValues[i]
val substitutedValue = state.substituteVariables(paramValue)
if (paramName.equals("file", ignoreCase = true) || paramName.equals("virtual", ignoreCase = true)) {
val virtual = paramName.equals("virtual", ignoreCase = true)
lastModified = state.ssiExternalResolver.getFileLastModified(substitutedValue, virtual)
val strftime = Strftime(state.configTimeFmt, Locale.US)
writer.write(strftime.format(Date(lastModified)))
}
else {
LOG.info("#flastmod--Invalid attribute: " + paramName);
writer.write(configErrMsg);
LOG.info("#flastmod--Invalid attribute: " + paramName)
writer.write(configErrMsg)
}
}
return lastModified;
return lastModified
}
});
commands.put("fsize", new SsiFsize());
commands.put("printenv", new SsiCommand() {
@Override
public long process(@NotNull SsiProcessingState state, @NotNull String commandName, @NotNull List<String> paramNames, @NotNull String[] paramValues, @NotNull ByteBufUtf8Writer writer) {
long lastModified = 0;
})
commands.put("fsize", SsiFsize())
commands.put("printenv", object : SsiCommand {
override fun process(state: SsiProcessingState, commandName: String, paramNames: List<String>, paramValues: Array<String>, writer: ByteBufUtf8Writer): Long {
var lastModified: Long = 0
// any arguments should produce an error
if (paramNames.isEmpty()) {
Set<String> variableNames = new LinkedHashSet<String>();
val variableNames = LinkedHashSet<String>()
//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");
state.ssiExternalResolver.addVariableNames(variableNames);
for (String variableName : variableNames) {
String variableValue = state.getVariableValue(variableName);
variableNames.add("DATE_GMT")
variableNames.add("DATE_LOCAL")
variableNames.add("LAST_MODIFIED")
state.ssiExternalResolver.addVariableNames(variableNames)
for (variableName in variableNames) {
var variableValue: String? = state.getVariableValue(variableName)
// This shouldn't happen, since all the variable names must have values
if (variableValue == null) {
variableValue = "(none)";
variableValue = "(none)"
}
writer.write(variableName);
writer.write('=');
writer.write(variableValue);
writer.write('\n');
lastModified = System.currentTimeMillis();
writer.append(variableName).append('=').append(variableValue).append('\n')
lastModified = System.currentTimeMillis()
}
}
else {
writer.write(state.configErrorMessage);
writer.write(state.configErrorMessage)
}
return lastModified;
return lastModified
}
});
commands.put("set", new SsiCommand() {
@Override
public long process(@NotNull SsiProcessingState state, @NotNull String commandName, @NotNull List<String> paramNames, @NotNull String[] paramValues, @NotNull ByteBufUtf8Writer writer) {
long lastModified = 0;
String errorMessage = state.configErrorMessage;
String variableName = null;
for (int i = 0; i < paramNames.size(); i++) {
String paramName = paramNames.get(i);
String paramValue = paramValues[i];
if (paramName.equalsIgnoreCase("var")) {
variableName = paramValue;
})
commands.put("set", object : SsiCommand {
override fun process(state: SsiProcessingState, commandName: String, paramNames: List<String>, paramValues: Array<String>, writer: ByteBufUtf8Writer): Long {
var lastModified: Long = 0
val errorMessage = state.configErrorMessage
var variableName: String? = null
for (i in paramNames.indices) {
val paramName = paramNames[i]
val paramValue = paramValues[i]
if (paramName.equals("var", ignoreCase = true)) {
variableName = paramValue
}
else if (paramName.equalsIgnoreCase("value")) {
else if (paramName.equals("value", ignoreCase = true)) {
if (variableName != null) {
String substitutedValue = state.substituteVariables(paramValue);
state.ssiExternalResolver.setVariableValue(variableName, substitutedValue);
lastModified = System.currentTimeMillis();
val substitutedValue = state.substituteVariables(paramValue)
state.ssiExternalResolver.setVariableValue(variableName, substitutedValue)
lastModified = System.currentTimeMillis()
}
else {
LOG.info("#set--no variable specified");
writer.write(errorMessage);
throw new SsiStopProcessingException();
LOG.info("#set--no variable specified")
writer.write(errorMessage)
throw SsiStopProcessingException()
}
}
else {
LOG.info("#set--Invalid attribute: " + paramName);
writer.write(errorMessage);
throw new SsiStopProcessingException();
LOG.info("#set--Invalid attribute: " + paramName)
writer.write(errorMessage)
throw SsiStopProcessingException()
}
}
return lastModified;
return lastModified
}
});
})
SsiConditional ssiConditional = new SsiConditional();
commands.put("if", ssiConditional);
commands.put("elif", ssiConditional);
commands.put("endif", ssiConditional);
commands.put("else", ssiConditional);
val ssiConditional = SsiConditional()
commands.put("if", ssiConditional)
commands.put("elif", ssiConditional)
commands.put("endif", ssiConditional)
commands.put("else", ssiConditional)
}
/**
* @return the most current modified date resulting from any SSI commands
*/
public long process(@NotNull SsiExternalResolver ssiExternalResolver, @NotNull String fileContents, long lastModifiedDate, @NotNull ByteBufUtf8Writer writer) {
SsiProcessingState ssiProcessingState = new SsiProcessingState(ssiExternalResolver, lastModifiedDate);
int index = 0;
boolean inside = false;
StringBuilder command = new StringBuilder();
fun process(ssiExternalResolver: SsiExternalResolver, fileContents: String, lastModifiedDate: Long, writer: ByteBufUtf8Writer): Long {
var resultLastModifiedDate = lastModifiedDate
val ssiProcessingState = SsiProcessingState(ssiExternalResolver, resultLastModifiedDate)
var index = 0
var inside = false
val command = StringBuilder()
try {
while (index < fileContents.length()) {
char c = fileContents.charAt(index);
while (index < fileContents.length) {
val c = fileContents[index]
if (inside) {
if (c == COMMAND_END.charAt(0) && charCmp(fileContents, index, COMMAND_END)) {
inside = false;
index += COMMAND_END.length();
String commandName = parseCommand(command);
if (LOG.isDebugEnabled()) {
LOG.debug("SSIProcessor.process -- processing command: " + commandName);
if (c == COMMAND_END[0] && charCmp(fileContents, index, COMMAND_END)) {
inside = false
index += COMMAND_END.length
val commandName = parseCommand(command)
if (LOG.isDebugEnabled) {
LOG.debug("SSIProcessor.process -- processing command: " + commandName)
}
List<String> paramNames = parseParamNames(command, commandName.length());
String[] paramValues = parseParamValues(command, commandName.length(), paramNames.size());
val paramNames = parseParamNames(command, commandName.length)
val paramValues = parseParamValues(command, commandName.length, paramNames.size)
// We need to fetch this value each time, since it may change during the loop
String configErrMsg = ssiProcessingState.configErrorMessage;
SsiCommand ssiCommand = commands.get(commandName.toLowerCase(Locale.ENGLISH));
String errorMessage = null;
val configErrMsg = ssiProcessingState.configErrorMessage
val ssiCommand = commands[commandName.toLowerCase(Locale.ENGLISH)]
var errorMessage: String? = null
if (ssiCommand == null) {
errorMessage = "Unknown command: " + commandName;
errorMessage = "Unknown command: " + commandName
}
else if (paramValues == null) {
errorMessage = "Error parsing directive parameters.";
errorMessage = "Error parsing directive parameters."
}
else if (paramNames.size() != paramValues.length) {
errorMessage = "Parameter names count does not match parameter values count on command: " + commandName;
else if (paramNames.size != paramValues.size) {
errorMessage = "Parameter names count does not match parameter values count on command: " + commandName
}
else {
// don't process the command if we are processing conditional commands only and the command is not conditional
if (!ssiProcessingState.conditionalState.processConditionalCommandsOnly || ssiCommand instanceof SsiConditional) {
long newLastModified = ssiCommand.process(ssiProcessingState, commandName, paramNames, paramValues, writer);
if (newLastModified > lastModifiedDate) {
lastModifiedDate = newLastModified;
if (!ssiProcessingState.conditionalState.processConditionalCommandsOnly || ssiCommand is SsiConditional) {
val newLastModified = ssiCommand.process(ssiProcessingState, commandName, paramNames, paramValues, writer)
if (newLastModified > resultLastModifiedDate) {
resultLastModifiedDate = newLastModified
}
}
}
if (errorMessage != null) {
LOG.warn(errorMessage);
writer.write(configErrMsg);
LOG.warn(errorMessage)
writer.write(configErrMsg)
}
}
else {
command.append(c);
index++;
command.append(c)
index++
}
}
else if (c == COMMAND_START.charAt(0) && charCmp(fileContents, index, COMMAND_START)) {
inside = true;
index += COMMAND_START.length();
command.setLength(0);
else if (c == COMMAND_START[0] && charCmp(fileContents, index, COMMAND_START)) {
inside = true
index += COMMAND_START.length
command.setLength(0)
}
else {
if (!ssiProcessingState.conditionalState.processConditionalCommandsOnly) {
writer.write(c);
writer.write(c.toInt())
}
index++;
index++
}
}
}
catch (SsiStopProcessingException e) {
catch (e: SsiStopProcessingException) {
//If we are here, then we have already stopped processing, so all is good
}
return lastModifiedDate;
return resultLastModifiedDate
}
@NotNull
protected List<String> parseParamNames(@NotNull StringBuilder command, int start) {
int bIdx = start;
List<String> values = new SmartList<String>();
boolean inside = false;
StringBuilder builder = new StringBuilder();
while (bIdx < command.length()) {
protected fun parseParamNames(command: StringBuilder, start: Int): List<String> {
var bIdx = start
val values = SmartList<String>()
var inside = false
val builder = StringBuilder()
while (bIdx < command.length) {
if (inside) {
while (bIdx < command.length() && command.charAt(bIdx) != '=') {
builder.append(command.charAt(bIdx));
bIdx++;
while (bIdx < command.length && command[bIdx] != '=') {
builder.append(command[bIdx])
bIdx++
}
values.add(builder.toString());
builder.setLength(0);
inside = false;
int quotes = 0;
boolean escaped = false;
for (; bIdx < command.length() && quotes != 2; bIdx++) {
char c = command.charAt(bIdx);
values.add(builder.toString())
builder.setLength(0)
inside = false
var quotes = 0
var escaped = false
while (bIdx < command.length && quotes != 2) {
val c = command[bIdx]
// Need to skip escaped characters
if (c == '\\' && !escaped) {
escaped = true;
continue;
escaped = true
bIdx++
continue
}
if (c == '"' && !escaped) {
quotes++;
quotes++
}
escaped = false;
escaped = false
bIdx++
}
}
else {
while (bIdx < command.length() && isSpace(command.charAt(bIdx))) {
bIdx++;
while (bIdx < command.length && isSpace(command[bIdx])) {
bIdx++
}
if (bIdx >= command.length()) {
break;
if (bIdx >= command.length) {
break
}
inside = true;
inside = true
}
}
return values;
return values
}
@SuppressWarnings("AssignmentToForLoopParameter")
protected String[] parseParamValues(@NotNull StringBuilder command, int start, int count) {
int valueIndex = 0;
boolean inside = false;
String[] values = new String[count];
StringBuilder builder = new StringBuilder();
char endQuote = 0;
for (int bIdx = start; bIdx < command.length(); bIdx++) {
protected fun parseParamValues(command: StringBuilder, start: Int, count: Int): Array<String>? {
var valueIndex = 0
var inside = false
val values = arrayOfNulls<String>(count)
val builder = StringBuilder()
var endQuote: Char = 0.toChar()
var bIdx = start
while (bIdx < command.length) {
if (!inside) {
while (bIdx < command.length() && !isQuote(command.charAt(bIdx))) {
bIdx++;
while (bIdx < command.length && !isQuote(command[bIdx])) {
bIdx++
}
if (bIdx >= command.length()) {
break;
if (bIdx >= command.length) {
break
}
inside = true;
endQuote = command.charAt(bIdx);
inside = true
endQuote = command[bIdx]
}
else {
boolean escaped = false;
for (; bIdx < command.length(); bIdx++) {
char c = command.charAt(bIdx);
var escaped = false
while (bIdx < command.length) {
val c = command[bIdx]
// Check for escapes
if (c == '\\' && !escaped) {
escaped = true;
continue;
escaped = true
bIdx++
continue
}
// If we reach the other " then stop
if (c == endQuote && !escaped) {
break;
break
}
// Since parsing of attributes and var
// substitution is done in separate places,
// we need to leave escape in the string
if (c == '$' && escaped) {
builder.append('\\');
builder.append('\\')
}
escaped = false;
builder.append(c);
escaped = false
builder.append(c)
bIdx++
}
// If we hit the end without seeing a quote
// the signal an error
if (bIdx == command.length()) {
return null;
if (bIdx == command.length) {
return null
}
values[valueIndex++] = builder.toString();
builder.setLength(0);
inside = false;
values[valueIndex++] = builder.toString()
builder.setLength(0)
inside = false
}
bIdx++
}
return values;
@Suppress("CAST_NEVER_SUCCEEDS")
return values as Array<String>
}
@NotNull
private String parseCommand(@NotNull StringBuilder instruction) {
int firstLetter = -1;
int lastLetter = -1;
for (int i = 0; i < instruction.length(); i++) {
char c = instruction.charAt(i);
private fun parseCommand(instruction: StringBuilder): String {
var firstLetter = -1
var lastLetter = -1
for (i in 0..instruction.length - 1) {
val c = instruction[i]
if (Character.isLetter(c)) {
if (firstLetter == -1) {
firstLetter = i;
firstLetter = i
}
lastLetter = i;
lastLetter = i
}
else if (isSpace(c)) {
if (lastLetter > -1) {
break;
break
}
}
else {
break;
break
}
}
return firstLetter == -1 ? "" : instruction.substring(firstLetter, lastLetter + 1);
return if (firstLetter == -1) "" else instruction.substring(firstLetter, lastLetter + 1)
}
protected boolean charCmp(String buf, int index, String command) {
return buf.regionMatches(index, command, 0, command.length());
}
protected fun charCmp(buf: String, index: Int, command: String) = buf.regionMatches(index, command, 0, command.length)
protected fun isSpace(c: Char) = c == ' ' || c == '\n' || c == '\t' || c == '\r'
protected boolean isSpace(char c) {
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
}
protected boolean isQuote(char c) {
return c == '\'' || c == '\"' || c == '`';
}
protected fun isQuote(c: Char) = c == '\'' || c == '\"' || c == '`'
}