Repository: struts Updated Branches: refs/heads/develop 49ecb5f9f -> da6ed911c
Extends parser to allow parse collections Project: http://git-wip-us.apache.org/repos/asf/struts/repo Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/c03962c8 Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/c03962c8 Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/c03962c8 Branch: refs/heads/develop Commit: c03962c8c0d972c7e17a9f6d247887754fbe7f3f Parents: 49ecb5f Author: Lukasz Lenart <lukaszlen...@apache.org> Authored: Sat Mar 22 09:41:00 2014 +0100 Committer: Lukasz Lenart <lukaszlen...@apache.org> Committed: Sat Mar 22 09:41:00 2014 +0100 ---------------------------------------------------------------------- .../opensymphony/xwork2/util/TextParseUtil.java | 87 ++++++++++++++++++++ .../xwork2/util/TextParseUtilTest.java | 45 +++++++++- 2 files changed, 130 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/struts/blob/c03962c8/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java ---------------------------------------------------------------------- diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java index 4b54e81..a3938ef 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java @@ -16,9 +16,13 @@ package com.opensymphony.xwork2.util; import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; import com.opensymphony.xwork2.inject.Container; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; +import java.util.Map; import java.util.Set; @@ -168,6 +172,89 @@ public class TextParseUtil { } /** + * @see #translateVariablesCollection(char[], String, ValueStack, boolean, ParsedValueEvaluator, int) + * + * @param expression + * @param stack + * @param excludeEmptyElements + * @param evaluator + * @return + */ + public static Collection<String> translateVariablesCollection(String expression, ValueStack stack, boolean excludeEmptyElements, ParsedValueEvaluator evaluator) { + return translateVariablesCollection(new char[]{'$', '%'}, expression, stack, excludeEmptyElements, evaluator, MAX_RECURSION); + } + + /** + * Resolves given expression on given ValueStack. If found element is a + * collection each element will be converted to String. If just a single + * object is found it is converted to String and wrapped in a collection. + * + * @param openChars + * @param expression + * @param stack + * @param excludeEmptyElements + * @param evaluator + * @param maxLoopCount + * @return + */ + public static Collection<String> translateVariablesCollection( + char[] openChars, String expression, final ValueStack stack, boolean excludeEmptyElements, + final ParsedValueEvaluator evaluator, int maxLoopCount) { + + ParsedValueEvaluator ognlEval = new ParsedValueEvaluator() { + public Object evaluate(String parsedValue) { + return stack.findValue(parsedValue); // no asType !!! + } + }; + + Map<String, Object> context = stack.getContext(); + TextParser parser = ((Container)context.get(ActionContext.CONTAINER)).getInstance(TextParser.class); + + Object result = parser.evaluate(openChars, expression, ognlEval, maxLoopCount); + + XWorkConverter conv = ((Container)context.get(ActionContext.CONTAINER)).getInstance(XWorkConverter.class); + + Collection<String> resultCol; + if (result instanceof Collection) { + @SuppressWarnings("unchecked") + Collection<Object> casted = (Collection<Object>)result; + resultCol = new ArrayList<String>(casted.size()); + for (Object element : casted) { + String stringElement = (String)conv.convertValue(context, element, String.class); + if (shallBeIncluded(stringElement, excludeEmptyElements)) { + if (evaluator != null) { + stringElement = evaluator.evaluate(stringElement).toString(); + } + resultCol.add(stringElement); + } + } + } else { + resultCol = new ArrayList<String>(1); + String stringResult = (String)conv.convertValue(context, result, String.class); + if (shallBeIncluded(stringResult, excludeEmptyElements)) { + if (evaluator != null) { + stringResult = evaluator.evaluate(stringResult).toString(); + } + resultCol.add(stringResult); + } + } + + return resultCol; + } + + /** + * Tests if given string is not null and not empty when excluding of empty + * elements is requested. + * + * @param str String to check. + * @param excludeEmptyElements Whether empty elements shall be excluded. + * @return True if given string can be included in collection. + */ + private static boolean shallBeIncluded(String str, boolean excludeEmptyElements) { + return !excludeEmptyElements || ((str != null) && (str.length() > 0)); + } + + /** * Returns a set from comma delimted Strings. * @param s The String to parse. * @return A set from comma delimted Strings. http://git-wip-us.apache.org/repos/asf/struts/blob/c03962c8/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java ---------------------------------------------------------------------- diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java index 45e7970..a624a10 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java @@ -15,11 +15,19 @@ */ package com.opensymphony.xwork2.util; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; + import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.XWorkTestCase; -import java.util.*; - /** * Unit test of {@link TextParseUtil}. * @@ -177,4 +185,37 @@ public class TextParseUtilTest extends XWorkTestCase { assertEquals("foo: ", s); } + public void testTranslateVariablesCollection() { + ValueStack stack = ActionContext.getContext().getValueStack(); + final List<String> list = new ArrayList<String>() {{ + add("val 1"); + add("val 2"); + }}; + stack.push(new HashMap<String, Object>() {{ put("list", list); }}); + + Collection<String> collection = TextParseUtil.translateVariablesCollection("${list}", stack, true, null); + + Assert.assertNotNull(collection); + Assert.assertEquals(2, collection.size()); + } + + public void testTranslateVariablesCollectionWithExpressions() { + ValueStack stack = ActionContext.getContext().getValueStack(); + final List<String> list = new ArrayList<String>() {{ + add("${val1}"); + add("%{val2}"); + }}; + stack.push(new HashMap<String, Object>() {{ put("list", list); put("val1", 1); put("val2", "Value 2"); }}); + + Collection<String> collection = TextParseUtil.translateVariablesCollection("${list}", stack, true, null); + + Assert.assertNotNull(collection); + Assert.assertEquals(2, collection.size()); + + // if this starts passing, probably an double evaluation expression vulnerability was introduced + // carefully review changes as this can affect users and allows break in intruders + Assert.assertEquals("${val1}", collection.toArray()[0]); + Assert.assertEquals("%{val2}", collection.toArray()[1]); + } + }