diff --git a/core/src/main/java/io/undertow/attribute/ExchangeAttributeParser.java b/core/src/main/java/io/undertow/attribute/ExchangeAttributeParser.java index e493bf32e0..7bd6c9e94c 100644 --- a/core/src/main/java/io/undertow/attribute/ExchangeAttributeParser.java +++ b/core/src/main/java/io/undertow/attribute/ExchangeAttributeParser.java @@ -75,6 +75,7 @@ public ExchangeAttribute parse(final String valueString) { final List attributes = new ArrayList<>(); int pos = 0; int state = 0; //0 = literal, 1 = %, 2 = %{, 3 = $, 4 = ${ + int braceDepth = 0; // Track the depth of curly braces for (int i = 0; i < valueString.length(); ++i) { char c = valueString.charAt(i); switch (state) { @@ -95,6 +96,7 @@ public ExchangeAttribute parse(final String valueString) { case 1: { if (c == '{') { state = 2; + braceDepth++; } else if (c == '%') { //literal percent attributes.add(wrap(new ConstantExchangeAttribute("%"))); @@ -108,16 +110,22 @@ public ExchangeAttribute parse(final String valueString) { break; } case 2: { - if (c == '}') { - attributes.add(wrap(parseSingleToken(valueString.substring(pos, i + 1)))); - pos = i + 1; - state = 0; + if (c == '{') { + braceDepth++; + } else if (c == '}') { + braceDepth--; + if (braceDepth == 0) { + attributes.add(wrap(parseSingleToken(valueString.substring(pos, i + 1)))); + pos = i + 1; + state = 0; + } } break; } case 3: { if (c == '{') { state = 4; + braceDepth++; } else if (c == '$') { //literal dollars attributes.add(wrap(new ConstantExchangeAttribute("$"))); @@ -132,30 +140,26 @@ public ExchangeAttribute parse(final String valueString) { } case 4: { if (c == '}') { - attributes.add(wrap(parseSingleToken(valueString.substring(pos, i + 1)))); - pos = i + 1; - state = 0; + braceDepth--; + if (braceDepth == 0) { + attributes.add(wrap(parseSingleToken(valueString.substring(pos, i + 1)))); + pos = i + 1; + state = 0; + } + } else if (c == '{') { + braceDepth++; } break; } - } } - switch (state) { - case 0: - case 1: - case 3:{ - if(pos != valueString.length()) { - attributes.add(wrap(parseSingleToken(valueString.substring(pos)))); - } - break; - } - case 2: - case 4: { - throw UndertowMessages.MESSAGES.mismatchedBraces(valueString); - } + if (state != 0 || braceDepth != 0) { + throw UndertowMessages.MESSAGES.mismatchedBraces(valueString); + } + if (pos != valueString.length()) { + attributes.add(wrap(parseSingleToken(valueString.substring(pos)))); } - if(attributes.size() == 1) { + if (attributes.size() == 1) { return attributes.get(0); } return new CompositeExchangeAttribute(attributes.toArray(new ExchangeAttribute[attributes.size()])); diff --git a/core/src/test/java/io/undertow/attribute/ExchangeAttributeParserTest.java b/core/src/test/java/io/undertow/attribute/ExchangeAttributeParserTest.java new file mode 100644 index 0000000000..17766511be --- /dev/null +++ b/core/src/test/java/io/undertow/attribute/ExchangeAttributeParserTest.java @@ -0,0 +1,57 @@ +/* + * 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.attribute; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; + +public class ExchangeAttributeParserTest { + + @Test + public void testSimpleNestedAttribute() { + ExchangeAttributeParser parser = new ExchangeAttributeParser(ExchangeAttributeParserTest.class.getClassLoader(), Collections.emptyList()); + String attributeString = "%{myCustomAttr:%{REQUEST_LINE}}"; + ExchangeAttribute attribute = parser.parse(attributeString); + // The attribute should be parsed as a single nested attribute, not as a composite of multiple attributes + Assert.assertEquals("Parsed attribute string should match input", attributeString, attribute.toString()); + Assert.assertFalse("Attribute should not be of CompositeExchangeAttribute type", attribute instanceof CompositeExchangeAttribute); + } + + @Test + public void testComplexNestedAttribute() { + ExchangeAttributeParser parser = new ExchangeAttributeParser(ExchangeAttributeParserTest.class.getClassLoader(), Collections.emptyList()); + String attributeString = "%{myCustomAttr:%{anotherCustomAttr:%{REQUEST_LINE}}-%{REQUEST_METHOD}}"; + ExchangeAttribute attribute = parser.parse(attributeString); + // The attribute should be parsed as a single nested attribute, not as a composite of multiple attributes + Assert.assertFalse("Attribute should not be of CompositeExchangeAttribute type", attribute instanceof CompositeExchangeAttribute); + Assert.assertEquals("Parsed attribute string should match input", attributeString, attribute.toString()); + } + + @Test + public void testSimpleAttribute() { + ExchangeAttributeParser parser = new ExchangeAttributeParser(ExchangeAttributeParserTest.class.getClassLoader(), Collections.emptyList()); + String attributeString = "%{REQUEST_LINE}"; + ExchangeAttribute attribute = parser.parse(attributeString); + Assert.assertTrue("Attribute should be of RequestLineAttribute type", attribute instanceof RequestLineAttribute); + Assert.assertEquals("Parsed attribute string should match input", attributeString, attribute.toString()); + } + +} \ No newline at end of file