package org.apache.catalina.valves.rewrite;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import org.apache.catalina.util.URLEncoder;
public class Substitution {
public abstract class SubstitutionElement {
public abstract String evaluate(Matcher rule, Matcher cond, Resolver resolver);
}
public class StaticElement extends SubstitutionElement {
public String value;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return value;
}
}
public class RewriteRuleBackReferenceElement extends SubstitutionElement {
public int n;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String result = rule.group(n);
if (result == null) {
result = "";
}
if (escapeBackReferences) {
return URLEncoder.DEFAULT.encode(result, resolver.getUriCharset());
} else {
return result;
}
}
}
public class RewriteCondBackReferenceElement extends SubstitutionElement {
public int n;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return (cond.group(n) == null ? "" : cond.group(n));
}
}
public class ServerVariableElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolve(key);
}
}
public class ServerVariableEnvElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveEnv(key);
}
}
public class ServerVariableSslElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveSsl(key);
}
}
public class ServerVariableHttpElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveHttp(key);
}
}
public class MapElement extends SubstitutionElement {
public RewriteMap map = null;
public SubstitutionElement[] defaultValue = null;
public SubstitutionElement[] key = null;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String result = map.lookup(evaluateSubstitution(key, rule, cond, resolver));
if (result == null && defaultValue != null) {
result = evaluateSubstitution(defaultValue, rule, cond, resolver);
}
return result;
}
}
protected SubstitutionElement[] elements = null;
protected String sub = null;
public String getSub() { return sub; }
public void setSub(String sub) { this.sub = sub; }
private boolean escapeBackReferences;
void setEscapeBackReferences(boolean escapeBackReferences) {
this.escapeBackReferences = escapeBackReferences;
}
public void parse(Map<String, RewriteMap> maps) {
this.elements = parseSubstitution(sub, maps);
}
private SubstitutionElement[] parseSubstitution(String sub, Map<String, RewriteMap> maps) {
List<SubstitutionElement> elements = new ArrayList<>();
int pos = 0;
int percentPos = 0;
int dollarPos = 0;
int backslashPos = 0;
while (pos < sub.length()) {
percentPos = sub.indexOf('%', pos);
dollarPos = sub.indexOf('$', pos);
backslashPos = sub.indexOf('\\', pos);
if (percentPos == -1 && dollarPos == -1 && backslashPos == -1) {
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, sub.length());
pos = sub.length();
elements.add(newElement);
} else if (isFirstPos(backslashPos, dollarPos, percentPos)) {
if (backslashPos + 1 == sub.length()) {
throw new IllegalArgumentException(sub);
}
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, backslashPos) + sub.substring(backslashPos + 1, backslashPos + 2);
pos = backslashPos + 2;
elements.add(newElement);
} else if (isFirstPos(dollarPos, percentPos)) {
if (dollarPos + 1 == sub.length()) {
throw new IllegalArgumentException(sub);
}
if (pos < dollarPos) {
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, dollarPos);
pos = dollarPos;
elements.add(newElement);
}
if (Character.isDigit(sub.charAt(dollarPos + 1))) {
RewriteRuleBackReferenceElement newElement = new RewriteRuleBackReferenceElement();
newElement.n = Character.digit(sub.charAt(dollarPos + 1), 10);
pos = dollarPos + 2;
elements.add(newElement);
} else if (sub.charAt(dollarPos + 1) == '{') {
MapElement newElement = new MapElement();
int open = sub.indexOf('{', dollarPos);
int colon = findMatchingColonOrBar(true, sub, open);
int def = findMatchingColonOrBar(false, sub, open);
int close = findMatchingBrace(sub, open);
if (!(-1 < open && open < colon && colon < close)) {
throw new IllegalArgumentException(sub);
}
newElement.map = maps.get(sub.substring(open + 1, colon));
if (newElement.map == null) {
throw new IllegalArgumentException(sub + ": No map: " + sub.substring(open + 1, colon));
}
String key = null;
String defaultValue = null;
if (def > -1) {
if (!(colon < def && def < close)) {
throw new IllegalArgumentException(sub);
}
key = sub.substring(colon + 1, def);
defaultValue = sub.substring(def + 1, close);
} else {
key = sub.substring(colon + 1, close);
}
newElement.key = parseSubstitution(key, maps);
if (defaultValue != null) {
newElement.defaultValue = parseSubstitution(defaultValue, maps);
}
pos = close + 1;
elements.add(newElement);
} else {
throw new IllegalArgumentException(sub + ": missing digit or curly brace.");
}
} else {
if (percentPos + 1 == sub.length()) {
throw new IllegalArgumentException(sub);
}
if (pos < percentPos) {
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, percentPos);
pos = percentPos;
elements.add(newElement);
}
if (Character.isDigit(sub.charAt(percentPos + 1))) {
RewriteCondBackReferenceElement newElement = new RewriteCondBackReferenceElement();
newElement.n = Character.digit(sub.charAt(percentPos + 1), 10);
pos = percentPos + 2;
elements.add(newElement);
} else if (sub.charAt(percentPos + 1) == '{') {
SubstitutionElement newElement = null;
int open = sub.indexOf('{', percentPos);
int colon = findMatchingColonOrBar(true, sub, open);
int close = findMatchingBrace(sub, open);
if (!(-1 < open && open < close)) {
throw new IllegalArgumentException(sub);
}
if (colon > -1 && open < colon && colon < close) {
String type = sub.substring(open + 1, colon);
if (type.equals("ENV")) {
newElement = new ServerVariableEnvElement();
((ServerVariableEnvElement) newElement).key = sub.substring(colon + 1, close);
} else if (type.equals("SSL")) {
newElement = new ServerVariableSslElement();
((ServerVariableSslElement) newElement).key = sub.substring(colon + 1, close);
} else if (type.equals("HTTP")) {
newElement = new ServerVariableHttpElement();
((ServerVariableHttpElement) newElement).key = sub.substring(colon + 1, close);
} else {
throw new IllegalArgumentException(sub + ": Bad type: " + type);
}
} else {
newElement = new ServerVariableElement();
((ServerVariableElement) newElement).key = sub.substring(open + 1, close);
}
pos = close + 1;
elements.add(newElement);
} else {
throw new IllegalArgumentException(sub + ": missing digit or curly brace.");
}
}
}
return elements.toArray(new SubstitutionElement[0]);
}
private static int findMatchingBrace(String sub, int start) {
int nesting = 1;
for (int i = start + 1; i < sub.length(); i++) {
char c = sub.charAt(i);
if (c == '{') {
char previousChar = sub.charAt(i-1);
if (previousChar == '$' || previousChar == '%') {
nesting++;
}
} else if (c == '}') {
nesting--;
if (nesting == 0) {
return i;
}
}
}
return -1;
}
private static int findMatchingColonOrBar(boolean colon, String sub, int start) {
int nesting = 0;
for (int i = start + 1; i < sub.length(); i++) {
char c = sub.charAt(i);
if (c == '{') {
char previousChar = sub.charAt(i-1);
if (previousChar == '$' || previousChar == '%') {
nesting++;
}
} else if (c == '}') {
nesting--;
} else if (colon ? c == ':' : c =='|') {
if (nesting == 0) {
return i;
}
}
}
return -1;
}
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return evaluateSubstitution(elements, rule, cond, resolver);
}
private String evaluateSubstitution(SubstitutionElement[] elements, Matcher rule, Matcher cond, Resolver resolver) {
StringBuilder buf = new StringBuilder();
for (SubstitutionElement element : elements) {
buf.append(element.evaluate(rule, cond, resolver));
}
return buf.toString();
}
private boolean isFirstPos(int testPos, int... others) {
if (testPos < 0) {
return false;
}
for (int other : others) {
if (other >= 0 && other < testPos) {
return false;
}
}
return true;
}
}