From 90425721b202c67d84acabd149d2ca4287adc358 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 10 Jun 2015 11:53:42 +0200 Subject: [PATCH] Fix issue with handlers parsing --- .../undertow/predicate/PredicateParser.java | 259 ++++++------------ .../handlers/builder/HandlerParser.java | 131 ++++----- .../builder/PredicatedHandlersParser.java | 34 ++- .../io/undertow/util/PredicateTokeniser.java | 163 +++++++++++ .../handlers/PredicatedHandlersTestCase.java | 3 +- 5 files changed, 340 insertions(+), 250 deletions(-) create mode 100644 core/src/main/java/io/undertow/util/PredicateTokeniser.java diff --git a/core/src/main/java/io/undertow/predicate/PredicateParser.java b/core/src/main/java/io/undertow/predicate/PredicateParser.java index 3e164b3886..3aab47adf2 100644 --- a/core/src/main/java/io/undertow/predicate/PredicateParser.java +++ b/core/src/main/java/io/undertow/predicate/PredicateParser.java @@ -35,6 +35,8 @@ import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeParser; import io.undertow.attribute.ExchangeAttributes; +import io.undertow.util.PredicateTokeniser; +import io.undertow.util.PredicateTokeniser.Token; /** * Parser that can build a predicate from a string representation. The underlying syntax is quite simple, and example is @@ -68,7 +70,14 @@ public class PredicateParser { public static final Predicate parse(String string, final ClassLoader classLoader) { final Map builders = loadBuilders(classLoader); final ExchangeAttributeParser attributeParser = ExchangeAttributes.parser(classLoader); - return parse(string, builders, attributeParser); + Deque tokens = PredicateTokeniser.tokenize(string); + return parse(string, tokens, builders, attributeParser); + } + + public static final Predicate parse(String string, Deque tokens, final ClassLoader classLoader) { + final Map builders = loadBuilders(classLoader); + final ExchangeAttributeParser attributeParser = ExchangeAttributes.parser(classLoader); + return parse(string, new ArrayDeque<>(tokens), builders, attributeParser); } private static Map loadBuilders(final ClassLoader classLoader) { @@ -86,22 +95,10 @@ private static Map loadBuilders(final ClassLoader clas return ret; } - private static IllegalStateException error(final String string, int pos, String reason) { - StringBuilder b = new StringBuilder(); - b.append(string); - b.append('\n'); - for (int i = 0; i < pos; ++i) { - b.append(' '); - } - b.append('^'); - throw UndertowMessages.MESSAGES.errorParsingPredicateString(reason, b.toString()); - } - - static Predicate parse(final String string, final Map builders, final ExchangeAttributeParser attributeParser) { + static Predicate parse(final String string, Deque tokens, final Map builders, final ExchangeAttributeParser attributeParser) { //shunting yard algorithm //gets rid or parentheses and fixes up operator ordering - Deque tokens = tokenize(string); Deque operatorStack = new ArrayDeque<>(); //the output, consisting of predicate nodes and string representations of operators @@ -110,14 +107,14 @@ static Predicate parse(final String string, final Map while (!tokens.isEmpty()) { Token token = tokens.poll(); - if (isSpecialChar(token.token)) { - if (token.token.equals("(")) { + if (isSpecialChar(token.getToken())) { + if (token.getToken().equals("(")) { operatorStack.push("("); - } else if (token.token.equals(")")) { + } else if (token.getToken().equals(")")) { for (; ; ) { String op = operatorStack.pop(); if (op == null) { - throw error(string, token.position, "Unexpected end of input"); + throw PredicateTokeniser.error(string, token.getPosition(), "Unexpected end of input"); } else if (op.equals("(")) { break; } else { @@ -125,11 +122,11 @@ static Predicate parse(final String string, final Map } } } else { - throw error(string, token.position, "Mismatched parenthesis"); + throw PredicateTokeniser.error(string, token.getPosition(), "Mismatched parenthesis"); } } else { - if (isOperator(token.token)) { - int prec = precedence(token.token); + if (isOperator(token.getToken())) { + int prec = precedence(token.getToken()); String top = operatorStack.peek(); while (top != null) { if (top.equals("(")) { @@ -143,7 +140,7 @@ static Predicate parse(final String string, final Map } top = operatorStack.peek(); } - operatorStack.push(token.token); + operatorStack.push(token.getToken()); } else { output.push(parsePredicate(string, token, tokens, builders, attributeParser)); } @@ -152,14 +149,14 @@ static Predicate parse(final String string, final Map while (!operatorStack.isEmpty()) { String op = operatorStack.pop(); if (op.equals(")")) { - throw error(string, string.length(), "Mismatched parenthesis"); + throw PredicateTokeniser.error(string, string.length(), "Mismatched parenthesis"); } output.push(op); } //now we have our tokens Predicate predicate = collapseOutput(output.pop(), output).resolve(); if (!output.isEmpty()) { - throw error(string, 0, "Invalid expression"); + throw PredicateTokeniser.error(string, 0, "Invalid expression"); } return predicate; @@ -186,20 +183,20 @@ private static Node collapseOutput(final Object token, final Deque token } private static Object parsePredicate(final String string, final Token token, final Deque tokens, final Map builders, final ExchangeAttributeParser attributeParser) { - if (token.token.equals("true")) { + if (token.getToken().equals("true")) { return new PredicateNode(TruePredicate.instance()); - } else if (token.token.equals("false")) { + } else if (token.getToken().equals("false")) { return new PredicateNode(FalsePredicate.instance()); } else { - PredicateBuilder builder = builders.get(token.token); + PredicateBuilder builder = builders.get(token.getToken()); if (builder == null) { - throw error(string, token.position, "no predicate named " + token.token + " known predicates: " + builders.keySet()); + throw PredicateTokeniser.error(string, token.getPosition(), "no predicate named " + token.getToken() + " known predicates: " + builders.keySet()); } Token next = tokens.peek(); String endChar = ")"; - if (next.token.equals("[") || next.token.equals("(")) { - if(next.token.equals("[")) { + if (next.getToken().equals("[") || next.getToken().equals("(")) { + if(next.getToken().equals("[")) { endChar = "]"; UndertowLogger.ROOT_LOGGER.oldStylePredicateSyntax(string); } @@ -208,99 +205,99 @@ private static Object parsePredicate(final String string, final Token token, fin tokens.poll(); next = tokens.poll(); if (next == null) { - throw error(string, string.length(), "Unexpected end of input"); + throw PredicateTokeniser.error(string, string.length(), "Unexpected end of input"); } - if (next.token.equals("{")) { - return handleSingleArrayValue(string, builder, tokens, next, attributeParser); + if (next.getToken().equals("{")) { + return handleSingleArrayValue(string, builder, tokens, next, attributeParser, endChar); } - while (!next.token.equals(endChar)) { + while (!next.getToken().equals(endChar)) { Token equals = tokens.poll(); if(equals == null) { - throw error(string, string.length(), "Unexpected end of input"); + throw PredicateTokeniser.error(string, string.length(), "Unexpected end of input"); } - if (!equals.token.equals("=")) { - if (equals.token.equals(endChar) && values.isEmpty()) { + if (!equals.getToken().equals("=")) { + if (equals.getToken().equals(endChar) && values.isEmpty()) { //single value case return handleSingleValue(string, builder, next, attributeParser); - } else if (equals.token.equals(",")) { + } else if (equals.getToken().equals(",")) { tokens.push(equals); tokens.push(next); - return handleSingleVarArgsValue(string, builder, tokens, next, attributeParser); + return handleSingleVarArgsValue(string, builder, tokens, next, attributeParser, endChar); } - throw error(string, equals.position, "Unexpected token"); + throw PredicateTokeniser.error(string, equals.getPosition(), "Unexpected token"); } Token value = tokens.poll(); if (value == null) { - throw error(string, string.length(), "Unexpected end of input"); + throw PredicateTokeniser.error(string, string.length(), "Unexpected end of input"); } - if (value.token.equals("{")) { - values.put(next.token, readArrayType(string, tokens, next, builder, attributeParser, "}")); + if (value.getToken().equals("{")) { + values.put(next.getToken(), readArrayType(string, tokens, next, builder, attributeParser, "}")); } else { - if (isOperator(value.token) || isSpecialChar(value.token)) { - throw error(string, value.position, "Unexpected token"); + if (isOperator(value.getToken()) || isSpecialChar(value.getToken())) { + throw PredicateTokeniser.error(string, value.getPosition(), "Unexpected token"); } - Class type = builder.parameters().get(next.token); + Class type = builder.parameters().get(next.getToken()); if (type == null) { - throw error(string, next.position, "Unexpected parameter " + next.token); + throw PredicateTokeniser.error(string, next.getPosition(), "Unexpected parameter " + next.getToken()); } - values.put(next.token, coerceToType(string, value, type, attributeParser)); + values.put(next.getToken(), coerceToType(string, value, type, attributeParser)); } next = tokens.poll(); if (next == null) { - throw error(string, string.length(), "Unexpected end of input"); + throw PredicateTokeniser.error(string, string.length(), "Unexpected end of input"); } - if (!next.token.equals(endChar)) { - if (!next.token.equals(",")) { - throw error(string, string.length(), "Expecting , or " + endChar); + if (!next.getToken().equals(endChar)) { + if (!next.getToken().equals(",")) { + throw PredicateTokeniser.error(string, string.length(), "Expecting , or " + endChar); } next = tokens.poll(); if (next == null) { - throw error(string, string.length(), "Unexpected end of input"); + throw PredicateTokeniser.error(string, string.length(), "Unexpected end of input"); } } } - checkParameters(string, next.position, values, builder); + checkParameters(string, next.getPosition(), values, builder); return new BuilderNode(builder, values); } else { - if (isSpecialChar(next.token)) { - throw error(string, next.position, "Unexpected character"); + if (isSpecialChar(next.getToken())) { + throw PredicateTokeniser.error(string, next.getPosition(), "Unexpected character"); } return new BuilderNode(builder); } } } - private static Node handleSingleArrayValue(final String string, final PredicateBuilder builder, final Deque tokens, final Token token, final ExchangeAttributeParser attributeParser) { + private static Node handleSingleArrayValue(final String string, final PredicateBuilder builder, final Deque tokens, final Token token, final ExchangeAttributeParser attributeParser, String endChar) { String sv = builder.defaultParameter(); if (sv == null) { - throw error(string, token.position, "default parameter not supported"); + throw PredicateTokeniser.error(string, token.getPosition(), "default parameter not supported"); } - Object array = readArrayType(string, tokens, new Token(sv, token.position), builder, attributeParser, "}"); + Object array = readArrayType(string, tokens, new Token(sv, token.getPosition()), builder, attributeParser, "}"); Token close = tokens.poll(); - if (!close.token.equals("]")) { - throw error(string, close.position, "expected ]"); + if (!close.getToken().equals(endChar)) { + throw PredicateTokeniser.error(string, close.getPosition(), "expected " + endChar); } return new BuilderNode(builder, Collections.singletonMap(sv, array)); } - private static Node handleSingleVarArgsValue(final String string, final PredicateBuilder builder, final Deque tokens, final Token token, final ExchangeAttributeParser attributeParser) { + private static Node handleSingleVarArgsValue(final String string, final PredicateBuilder builder, final Deque tokens, final Token token, final ExchangeAttributeParser attributeParser, String endChar) { String sv = builder.defaultParameter(); if (sv == null) { - throw error(string, token.position, "default parameter not supported"); + throw PredicateTokeniser.error(string, token.getPosition(), "default parameter not supported"); } - Object array = readArrayType(string, tokens, new Token(sv, token.position), builder, attributeParser, "]"); + Object array = readArrayType(string, tokens, new Token(sv, token.getPosition()), builder, attributeParser, endChar); return new BuilderNode(builder, Collections.singletonMap(sv, array)); } private static Object readArrayType(final String string, final Deque tokens, Token paramName, PredicateBuilder builder, final ExchangeAttributeParser attributeParser, String expectedEndToken) { - Class type = builder.parameters().get(paramName.token); + Class type = builder.parameters().get(paramName.getToken()); if (type == null) { - throw error(string, paramName.position, "no parameter called " + paramName.token); + throw PredicateTokeniser.error(string, paramName.getPosition(), "no parameter called " + paramName.getToken()); } else if (!type.isArray()) { - throw error(string, paramName.position, "parameter is not an array type " + paramName.token); + throw PredicateTokeniser.error(string, paramName.getPosition(), "parameter is not an array type " + paramName.getToken()); } Class componentType = type.getComponentType(); @@ -309,28 +306,28 @@ private static Object readArrayType(final String string, final Deque toke while (token != null) { Token commaOrEnd = tokens.poll(); values.add(coerceToType(string, token, componentType, attributeParser)); - if (commaOrEnd.token.equals(expectedEndToken)) { + if (commaOrEnd.getToken().equals(expectedEndToken)) { Object array = Array.newInstance(componentType, values.size()); for (int i = 0; i < values.size(); ++i) { Array.set(array, i, values.get(i)); } return array; - } else if (!commaOrEnd.token.equals(",")) { - throw error(string, commaOrEnd.position, "expected either , or }"); + } else if (!commaOrEnd.getToken().equals(",")) { + throw PredicateTokeniser.error(string, commaOrEnd.getPosition(), "expected either , or }"); } token = tokens.poll(); } - throw error(string, string.length(), "unexpected end of input in array"); + throw PredicateTokeniser.error(string, string.length(), "unexpected end of input in array"); } private static Object handleSingleValue(final String string, final PredicateBuilder builder, final Token next, final ExchangeAttributeParser attributeParser) { String sv = builder.defaultParameter(); if (sv == null) { - throw error(string, next.position, "default parameter not supported"); + throw PredicateTokeniser.error(string, next.getPosition(), "default parameter not supported"); } Map values = Collections.singletonMap(sv, coerceToType(string, next, builder.parameters().get(sv), attributeParser)); - checkParameters(string, next.position, values, builder); + checkParameters(string, next.getPosition(), values, builder); return new BuilderNode(builder, values); } @@ -340,7 +337,7 @@ private static void checkParameters(final String string, int pos, final Map tokenize(final String string) { - char currentStringDelim = 0; - boolean inVariable = false; - - int pos = 0; - StringBuilder current = new StringBuilder(); - Deque ret = new ArrayDeque<>(); - while (pos < string.length()) { - char c = string.charAt(pos); - if (currentStringDelim != 0) { - if (c == currentStringDelim && current.charAt(current.length() - 1) != '\\') { - ret.add(new Token(current.toString(), pos)); - current.setLength(0); - currentStringDelim = 0; - } else { - current.append(c); - } - } else { - switch (c) { - case ' ': - case '\t': { - if (current.length() != 0) { - ret.add(new Token(current.toString(), pos)); - current.setLength(0); - } - break; - } - case '(': - case ')': - case ',': - case '=': - case '[': - case ']': - case '{': - case '}': { - if (inVariable) { - current.append(c); - if (c == '}') { - inVariable = false; - } - } else { - if (current.length() != 0) { - ret.add(new Token(current.toString(), pos)); - current.setLength(0); - } - ret.add(new Token("" + c, pos)); - } - break; - } - case '"': - case '\'': { - if (current.length() != 0) { - throw error(string, pos, "Unexpected token"); - } - currentStringDelim = c; - break; - } - case '%': - case '$': { - current.append(c); - if (string.charAt(pos + 1) == '{') { - inVariable = true; - } - break; - } - default: - current.append(c); - } - } - ++pos; - } - if (current.length() > 0) { - ret.add(new Token(current.toString(), string.length())); - } - return ret; - } - - - static final class Token { - final String token; - final int position; - - private Token(final String token, final int position) { - this.token = token; - this.position = position; - } - } - private interface Node { Predicate resolve(); diff --git a/core/src/main/java/io/undertow/server/handlers/builder/HandlerParser.java b/core/src/main/java/io/undertow/server/handlers/builder/HandlerParser.java index 67cbf20b01..490cac15d1 100644 --- a/core/src/main/java/io/undertow/server/handlers/builder/HandlerParser.java +++ b/core/src/main/java/io/undertow/server/handlers/builder/HandlerParser.java @@ -24,6 +24,7 @@ import io.undertow.attribute.ExchangeAttributeParser; import io.undertow.attribute.ExchangeAttributes; import io.undertow.server.HandlerWrapper; +import io.undertow.util.PredicateTokeniser.Token; import java.lang.reflect.Array; import java.util.ArrayDeque; @@ -59,7 +60,15 @@ public class HandlerParser { public static final HandlerWrapper parse(String string, final ClassLoader classLoader) { final Map builders = loadBuilders(classLoader); final ExchangeAttributeParser attributeParser = ExchangeAttributes.parser(classLoader); - return parse(string, builders, attributeParser); + Deque tokens = tokenize(string); + return parse(string, tokens, builders, attributeParser); + } + + + public static final HandlerWrapper parse(String string, Deque tokens, final ClassLoader classLoader) { + final Map builders = loadBuilders(classLoader); + final ExchangeAttributeParser attributeParser = ExchangeAttributes.parser(classLoader); + return parse(string, new ArrayDeque(tokens), builders, attributeParser); } private static Map loadBuilders(final ClassLoader classLoader) { @@ -97,15 +106,19 @@ static HandlerWrapper parse(final String string, final Map tokens, final Map builders, final ExchangeAttributeParser attributeParser) { + return parseBuilder(string, tokens.pop(), tokens, builders, attributeParser); + } + private static HandlerWrapper parseBuilder(final String string, final Token token, final Deque tokens, final Map builders, final ExchangeAttributeParser attributeParser) { - HandlerBuilder builder = builders.get(token.token); + HandlerBuilder builder = builders.get(token.getToken()); if (builder == null) { - throw error(string, token.position, "no predicate named " + token.token); + throw error(string, token.getPosition(), "no handler named " + token.getToken()); } Token next = tokens.peek(); String endChar = ")"; - if (next.token.equals("(") || next.token.equals("[")) { - if(next.token.equals("[")) { + if (next.getToken().equals("(") || next.getToken().equals("[")) { + if(next.getToken().equals("[")) { UndertowLogger.ROOT_LOGGER.oldStylePredicateSyntax(string); endChar = "]"; } @@ -116,46 +129,46 @@ private static HandlerWrapper parseBuilder(final String string, final Token toke if (next == null) { throw error(string, string.length(), "Unexpected end of input"); } - if (next.token.equals("{")) { - return handleSingleArrayValue(string, builder, tokens, next, attributeParser); + if (next.getToken().equals("{")) { + return handleSingleArrayValue(string, builder, tokens, next, attributeParser, endChar); } - while (!next.token.equals(endChar)) { + while (!next.getToken().equals(endChar)) { Token equals = tokens.poll(); - if (!equals.token.equals("=")) { - if (equals.token.equals("]") && values.isEmpty()) { + if (!equals.getToken().equals("=")) { + if (equals.getToken().equals(endChar) && values.isEmpty()) { //single value case return handleSingleValue(string, builder, next, attributeParser); - } else if (equals.token.equals(",")) { + } else if (equals.getToken().equals(",")) { tokens.push(equals); tokens.push(next); - return handleSingleVarArgsValue(string, builder, tokens, next, attributeParser); + return handleSingleVarArgsValue(string, builder, tokens, next, attributeParser, endChar); } - throw error(string, equals.position, "Unexpected token"); + throw error(string, equals.getPosition(), "Unexpected token"); } Token value = tokens.poll(); if (value == null) { throw error(string, string.length(), "Unexpected end of input"); } - if (value.token.equals("{")) { - values.put(next.token, readArrayType(string, tokens, next, builder, attributeParser, "}")); + if (value.getToken().equals("{")) { + values.put(next.getToken(), readArrayType(string, tokens, next, builder, attributeParser, "}")); } else { - if (isOperator(value.token) || isSpecialChar(value.token)) { - throw error(string, value.position, "Unexpected token"); + if (isOperator(value.getToken()) || isSpecialChar(value.getToken())) { + throw error(string, value.getPosition(), "Unexpected token"); } - Class type = builder.parameters().get(next.token); + Class type = builder.parameters().get(next.getToken()); if (type == null) { - throw error(string, next.position, "Unexpected parameter " + next.token); + throw error(string, next.getPosition(), "Unexpected parameter " + next.getToken()); } - values.put(next.token, coerceToType(string, value, type, attributeParser)); + values.put(next.getToken(), coerceToType(string, value, type, attributeParser)); } next = tokens.poll(); if (next == null) { throw error(string, string.length(), "Unexpected end of input"); } - if (!next.token.equals(endChar)) { - if (!next.token.equals(",")) { + if (!next.getToken().equals(endChar)) { + if (!next.getToken().equals(",")) { throw error(string, string.length(), "Expecting , or " + endChar); } next = tokens.poll(); @@ -164,42 +177,42 @@ private static HandlerWrapper parseBuilder(final String string, final Token toke } } } - checkParameters(string, next.position, values, builder); + checkParameters(string, next.getPosition(), values, builder); return builder.build(values); } else { - throw error(string, next.position, "Unexpected character"); + throw error(string, next.getPosition(), "Unexpected character"); } } - private static HandlerWrapper handleSingleArrayValue(final String string, final HandlerBuilder builder, final Deque tokens, final Token token, final ExchangeAttributeParser attributeParser) { + private static HandlerWrapper handleSingleArrayValue(final String string, final HandlerBuilder builder, final Deque tokens, final Token token, final ExchangeAttributeParser attributeParser, String endChar) { String sv = builder.defaultParameter(); if (sv == null) { - throw error(string, token.position, "default parameter not supported"); + throw error(string, token.getPosition(), "default parameter not supported"); } - Object array = readArrayType(string, tokens, new Token(sv, token.position), builder, attributeParser, "}"); + Object array = readArrayType(string, tokens, new Token(sv, token.getPosition()), builder, attributeParser, "}"); Token close = tokens.poll(); - if (!close.token.equals("]")) { - throw error(string, close.position, "expected ]"); + if (!close.getToken().equals(endChar)) { + throw error(string, close.getPosition(), "expected " + endChar); } return builder.build(Collections.singletonMap(sv, array)); } - private static HandlerWrapper handleSingleVarArgsValue(final String string, final HandlerBuilder builder, final Deque tokens, final Token token, final ExchangeAttributeParser attributeParser) { + private static HandlerWrapper handleSingleVarArgsValue(final String string, final HandlerBuilder builder, final Deque tokens, final Token token, final ExchangeAttributeParser attributeParser, String endChar) { String sv = builder.defaultParameter(); if (sv == null) { - throw error(string, token.position, "default parameter not supported"); + throw error(string, token.getPosition(), "default parameter not supported"); } - Object array = readArrayType(string, tokens, new Token(sv, token.position), builder, attributeParser, "]"); + Object array = readArrayType(string, tokens, new Token(sv, token.getPosition()), builder, attributeParser, endChar); return builder.build(Collections.singletonMap(sv, array)); } private static Object readArrayType(final String string, final Deque tokens, Token paramName, HandlerBuilder builder, final ExchangeAttributeParser attributeParser, String expectedEndToken) { - Class type = builder.parameters().get(paramName.token); + Class type = builder.parameters().get(paramName.getToken()); if (type == null) { - throw error(string, paramName.position, "no parameter called " + paramName.token); + throw error(string, paramName.getPosition(), "no parameter called " + paramName.getToken()); } else if (!type.isArray()) { - throw error(string, paramName.position, "parameter is not an array type " + paramName.token); + throw error(string, paramName.getPosition(), "parameter is not an array type " + paramName.getToken()); } Class componentType = type.getComponentType(); @@ -208,14 +221,14 @@ private static Object readArrayType(final String string, final Deque toke while (token != null) { Token commaOrEnd = tokens.poll(); values.add(coerceToType(string, token, componentType, attributeParser)); - if (commaOrEnd.token.equals(expectedEndToken)) { + if (commaOrEnd.getToken().equals(expectedEndToken)) { Object array = Array.newInstance(componentType, values.size()); for (int i = 0; i < values.size(); ++i) { Array.set(array, i, values.get(i)); } return array; - } else if (!commaOrEnd.token.equals(",")) { - throw error(string, commaOrEnd.position, "expected either , or }"); + } else if (!commaOrEnd.getToken().equals(",")) { + throw error(string, commaOrEnd.getPosition(), "expected either , or }"); } token = tokens.poll(); } @@ -226,10 +239,10 @@ private static Object readArrayType(final String string, final Deque toke private static HandlerWrapper handleSingleValue(final String string, final HandlerBuilder builder, final Token next, final ExchangeAttributeParser attributeParser) { String sv = builder.defaultParameter(); if (sv == null) { - throw error(string, next.position, "default parameter not supported"); + throw error(string, next.getPosition(), "default parameter not supported"); } Map values = Collections.singletonMap(sv, coerceToType(string, next, builder.parameters().get(sv), attributeParser)); - checkParameters(string, next.position, values, builder); + checkParameters(string, next.getPosition(), values, builder); return builder.build(values); } @@ -252,31 +265,31 @@ private static Object coerceToType(final String string, final Token token, final } if (type == String.class) { - return token.token; + return token.getToken(); } else if (type.equals(Boolean.class) || type.equals(boolean.class)) { - return Boolean.valueOf(token.token); + return Boolean.valueOf(token.getToken()); } else if (type.equals(Byte.class) || type.equals(byte.class)) { - return Byte.valueOf(token.token); + return Byte.valueOf(token.getToken()); } else if (type.equals(Character.class) || type.equals(char.class)) { - if (token.token.length() != 1) { - throw error(string, token.position, "Cannot coerce " + token.token + " to a Character"); + if (token.getToken().length() != 1) { + throw error(string, token.getPosition(), "Cannot coerce " + token.getToken() + " to a Character"); } - return Character.valueOf(token.token.charAt(0)); + return Character.valueOf(token.getToken().charAt(0)); } else if (type.equals(Short.class) || type.equals(short.class)) { - return Short.valueOf(token.token); + return Short.valueOf(token.getToken()); } else if (type.equals(Integer.class) || type.equals(int.class)) { - return Integer.valueOf(token.token); + return Integer.valueOf(token.getToken()); } else if (type.equals(Long.class) || type.equals(long.class)) { - return Long.valueOf(token.token); + return Long.valueOf(token.getToken()); } else if (type.equals(Float.class) || type.equals(float.class)) { - return Float.valueOf(token.token); + return Float.valueOf(token.getToken()); } else if (type.equals(Double.class) || type.equals(double.class)) { - return Double.valueOf(token.token); + return Double.valueOf(token.getToken()); } else if (type.equals(ExchangeAttribute.class)) { - return attributeParser.parse(token.token); + return attributeParser.parse(token.getToken()); } - return token.token; + return token.getToken(); } private static int precedence(String operator) { @@ -390,16 +403,4 @@ static Deque tokenize(final String string) { } return ret; } - - - static final class Token { - final String token; - final int position; - - private Token(final String token, final int position) { - this.token = token; - this.position = position; - } - } - } diff --git a/core/src/main/java/io/undertow/server/handlers/builder/PredicatedHandlersParser.java b/core/src/main/java/io/undertow/server/handlers/builder/PredicatedHandlersParser.java index d77cde3c8e..d18f31a3db 100644 --- a/core/src/main/java/io/undertow/server/handlers/builder/PredicatedHandlersParser.java +++ b/core/src/main/java/io/undertow/server/handlers/builder/PredicatedHandlersParser.java @@ -24,14 +24,18 @@ import io.undertow.server.HandlerWrapper; import io.undertow.util.ChainedHandlerWrapper; import io.undertow.util.FileUtils; +import io.undertow.util.PredicateTokeniser; +import io.undertow.util.PredicateTokeniser.Token; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Deque; import java.util.List; /** @@ -66,20 +70,32 @@ public static List parse(final String contents, final ClassLo for (String line : lines) { if (line.trim().length() > 0) { + Deque tokens = PredicateTokeniser.tokenize(line); + List> others = new ArrayList<>(); Predicate predicate; HandlerWrapper handler; - String[] parts = line.split("->"); - if (parts.length == 2) { - predicate = PredicateParser.parse(parts[0], classLoader); - handler = HandlerParser.parse(parts[1], classLoader); - } else if (parts.length == 1) { + Deque predicatePart = new ArrayDeque<>(); + Deque current = predicatePart; + while (!tokens.isEmpty()) { + Token token = tokens.poll(); + if(token.getToken().equals("->")) { + current = new ArrayDeque<>(); + others.add(current); + } else { + current.add(token); + } + } + if (others.isEmpty()) { predicate = Predicates.truePredicate(); - handler = HandlerParser.parse(parts[0], classLoader); + handler = HandlerParser.parse(line, predicatePart, classLoader); + } else if(others.size() == 1){ + predicate = PredicateParser.parse(line, predicatePart, classLoader); + handler = HandlerParser.parse(line, others.get(0), classLoader); } else { - predicate = PredicateParser.parse(parts[0], classLoader); - HandlerWrapper[] handlers = new HandlerWrapper[parts.length -1]; + predicate = PredicateParser.parse(line, predicatePart, classLoader); + HandlerWrapper[] handlers = new HandlerWrapper[others.size()]; for(int i = 0; i < handlers.length; ++i) { - handlers[i] = HandlerParser.parse(parts[i + 1], classLoader); + handlers[i] = HandlerParser.parse(line, others.get(i), classLoader); } handler = new ChainedHandlerWrapper(Arrays.asList(handlers)); } diff --git a/core/src/main/java/io/undertow/util/PredicateTokeniser.java b/core/src/main/java/io/undertow/util/PredicateTokeniser.java new file mode 100644 index 0000000000..77ace3a568 --- /dev/null +++ b/core/src/main/java/io/undertow/util/PredicateTokeniser.java @@ -0,0 +1,163 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.util; + +import io.undertow.UndertowMessages; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * Tokeniser that is re-used by the predicate and handler parsers, as well as the combined predicated + * handlers parser. + * + * @author Stuart Douglas + */ +public class PredicateTokeniser { + + + public static Deque tokenize(final String string) { + char currentStringDelim = 0; + boolean inVariable = false; + + int pos = 0; + StringBuilder current = new StringBuilder(); + Deque ret = new ArrayDeque<>(); + while (pos < string.length()) { + char c = string.charAt(pos); + if (currentStringDelim != 0) { + if (c == currentStringDelim && current.charAt(current.length() - 1) != '\\') { + ret.add(new Token(current.toString(), pos)); + current.setLength(0); + currentStringDelim = 0; + } else { + current.append(c); + } + } else { + switch (c) { + case ' ': + case '\t': { + if (current.length() != 0) { + ret.add(new Token(current.toString(), pos)); + current.setLength(0); + } + break; + } + case '(': + case ')': + case ',': + case '=': + case '[': + case ']': + case '{': + case '}': { + if (inVariable) { + current.append(c); + if (c == '}') { + inVariable = false; + } + } else { + if (current.length() != 0) { + ret.add(new Token(current.toString(), pos)); + current.setLength(0); + } + ret.add(new Token("" + c, pos)); + } + break; + } + case '"': + case '\'': { + if (current.length() != 0) { + throw error(string, pos, "Unexpected token"); + } + currentStringDelim = c; + break; + } + case '%': + case '$': { + current.append(c); + if (string.charAt(pos + 1) == '{') { + inVariable = true; + } + break; + } + case '-': + if (inVariable) { + current.append(c); + } else { + if (pos != string.length() && string.charAt(pos + 1) == '>') { + pos++; + if (current.length() != 0) { + ret.add(new Token(current.toString(), pos)); + current.setLength(0); + } + ret.add(new Token("->", pos)); + } else { + current.append(c); + } + } + break; + default: + current.append(c); + } + } + ++pos; + } + if (current.length() > 0) { + ret.add(new Token(current.toString(), string.length())); + } + return ret; + } + + public static final class Token { + private final String token; + private final int position; + + public Token(final String token, final int position) { + this.token = token; + this.position = position; + } + + public String getToken() { + return token; + } + + public int getPosition() { + return position; + } + + @Override + public String toString() { + return token + " <" + position + ">"; + } + } + + + public static IllegalStateException error(final String string, int pos, String reason) { + StringBuilder b = new StringBuilder(); + b.append(string); + b.append('\n'); + for (int i = 0; i < pos; ++i) { + b.append(' '); + } + b.append('^'); + throw UndertowMessages.MESSAGES.errorParsingPredicateString(reason, b.toString()); + } + +} diff --git a/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java b/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java index 11cde83538..7479803e82 100644 --- a/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java +++ b/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java @@ -50,7 +50,8 @@ public void testRewrite() throws IOException { "regex('(.*).css') -> rewrite['${1}.xcss'] -> set[attribute='%{o,chained}', value=true]\n" + "regex('(.*).redirect$') -> redirect['${1}.redirected']\n" + "set[attribute='%{o,someHeader}', value=always]\n" + - "path-template('/foo/{bar}/{f}') -> set[attribute='%{o,template}', value='${bar}']", getClass().getClassLoader()), new HttpHandler() { + "path-template('/foo/{bar}/{f}') -> set[attribute='%{o,template}', value='${bar}']\n" + + "path-template('/bar->foo') -> redirect(/)", getClass().getClassLoader()), new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRelativePath());