From 8ec95898dbd112648d0fbce022579bcb2f49b0dd Mon Sep 17 00:00:00 2001 From: Peter Kajsa Date: Wed, 8 Mar 2017 13:54:22 +0100 Subject: [PATCH] Bug 5410 - XSD regular expressions are interpreted as Java regexes (1/2) As both '^' and '$' are special anchor characters in java regular expressions which are implicitly present in XSD regular expressions, we need to escape them in case they are not defined as part of character ranges i.e. inside regular square brackets. Change-Id: Iafbf350f88ebdf96c30e1ccedbd00b90a93d521a Signed-off-by: Peter Kajsa --- .../model/api/type/PatternConstraint.java | 14 +- .../model/export/SchemaContextEmitter.java | 2 +- .../stmt/rfc6020/PatternStatementImpl.java | 55 +++- .../AbstractConstraintEffectiveStatement.java | 5 +- .../type/PatternConstraintEffectiveImpl.java | 17 +- .../yang/parser/stmt/rfc6020/Bug5410Test.java | 238 ++++++++++++++++++ .../src/test/resources/bugs/bug5410/foo.yang | 10 + 7 files changed, 329 insertions(+), 12 deletions(-) create mode 100644 yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java create mode 100644 yang/yang-parser-impl/src/test/resources/bugs/bug5410/foo.yang diff --git a/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/type/PatternConstraint.java b/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/type/PatternConstraint.java index 14844c64a9..a20c4d3834 100644 --- a/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/type/PatternConstraint.java +++ b/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/type/PatternConstraint.java @@ -17,13 +17,23 @@ import org.opendaylight.yangtools.yang.model.api.ConstraintMetaDefinition; public interface PatternConstraint extends ConstraintMetaDefinition { /** - * Returns a regular expression (pattern). + * Returns a java regular expression (pattern). * - * @return string with regular expression which is equal to the argument of + * @return string with java regular expression which is equal to the argument of * the YANG pattern substatement */ String getRegularExpression(); + /** + * Returns a raw regular expression as it was declared in a source. + * + * @return argument of pattern statement as it was declared in a source. + */ + // FIXME: version 2.0.0: make this method non-default + default String getRawRegularExpression() { + return getRegularExpression(); + } + /** * All implementations should override this method. * The default definition of this method is used only in YANG 1.0 (RFC6020) diff --git a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/SchemaContextEmitter.java b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/SchemaContextEmitter.java index ead600cbc7..9ec95a5b11 100644 --- a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/SchemaContextEmitter.java +++ b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/SchemaContextEmitter.java @@ -573,7 +573,7 @@ class SchemaContextEmitter { } private void emitPatternNode(final PatternConstraint pattern) { - writer.startPatternNode(pattern.getRegularExpression()); + writer.startPatternNode(pattern.getRawRegularExpression()); emitErrorMessageNode(pattern.getErrorMessage()); // FIXME: BUG-2444: Optional emitErrorAppTagNode(pattern.getErrorAppTag()); // FIXME: BUG-2444: Optional emitDescriptionNode(pattern.getDescription()); diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java index e711624e07..d899ed879f 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java @@ -53,7 +53,7 @@ public class PatternStatementImpl extends AbstractDeclaredStatement ctx, final String value) { - final String pattern = "^" + Utils.fixUnicodeScriptPattern(value) + '$'; + final String pattern = getJavaRegexFromXSD(value); try { Pattern.compile(pattern); @@ -62,7 +62,58 @@ public class PatternStatementImpl extends AbstractDeclaredStatement stmt) { - return new PatternConstraintEffectiveImpl(patternConstraint.getRegularExpression(), stmt.getDescription(), - stmt.getReference(), stmt.getErrorAppTag(), stmt.getErrorMessage(), stmt.getModifier()); + return new PatternConstraintEffectiveImpl(patternConstraint.getRegularExpression(), + patternConstraint.getRawRegularExpression(), stmt.getDescription(), stmt.getReference(), + stmt.getErrorAppTag(), stmt.getErrorMessage(), stmt.getModifier()); } } \ No newline at end of file diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/type/PatternConstraintEffectiveImpl.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/type/PatternConstraintEffectiveImpl.java index ace88ab63f..95c4d89f99 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/type/PatternConstraintEffectiveImpl.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/type/PatternConstraintEffectiveImpl.java @@ -17,21 +17,23 @@ import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint; public class PatternConstraintEffectiveImpl implements PatternConstraint { private final String regEx; + private final String rawRegEx; private final String description; private final String reference; private final String errorAppTag; private final String errorMessage; private final ModifierKind modifier; - public PatternConstraintEffectiveImpl(final String regex, final Optional description, - final Optional reference) { - this(regex, description.orNull(), reference.orNull(), null, null, null); + public PatternConstraintEffectiveImpl(final String regex, final String rawRegex, + final Optional description, final Optional reference) { + this(regex, rawRegex, description.orNull(), reference.orNull(), null, null, null); } - public PatternConstraintEffectiveImpl(final String regex, final String description, final String reference, - final String errorAppTag, final String errorMessage, final ModifierKind modifier) { + public PatternConstraintEffectiveImpl(final String regex, final String rawRegex, final String description, + final String reference, final String errorAppTag, final String errorMessage, final ModifierKind modifier) { super(); this.regEx = Preconditions.checkNotNull(regex, "regex must not be null."); + this.rawRegEx = Preconditions.checkNotNull(rawRegex, "raw regex must not be null."); this.description = description; this.reference = reference; this.errorAppTag = errorAppTag != null ? errorAppTag : "invalid-regular-expression"; @@ -45,6 +47,11 @@ public class PatternConstraintEffectiveImpl implements PatternConstraint { return regEx; } + @Override + public String getRawRegularExpression() { + return rawRegEx; + } + @Override public String getDescription() { return description; diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java new file mode 100644 index 0000000000..525302b9b8 --- /dev/null +++ b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.stmt.rfc6020; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.List; +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint; +import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition; +import org.opendaylight.yangtools.yang.stmt.StmtTestUtils; + +public class Bug5410Test { + private static final String FOO_NS = "foo"; + private static final String FOO_REV = "1970-01-01"; + + @Test + public void testJavaRegexFromXSD() { + testPattern("^[^:]+$", "^\\^[^:]+\\$$", ImmutableList.of("^a$", "^abc$"), + ImmutableList.of("abc$", "^abc", "^a:bc$")); + testPattern("^[$^]$", "^\\^[$^]\\$$", ImmutableList.of("^^$", "^$$"), ImmutableList.of("^^", "^$", "$^", "$$")); + testPattern("[$-%]+", "^[$-%]+$", ImmutableList.of("$", "%", "%$"), ImmutableList.of("$-", "$-%", "-", "^")); + testPattern("[$-&]+", "^[$-&]+$", ImmutableList.of("$", "%&", "%$", "$%&"), ImmutableList.of("#", "$-&", "'")); + + testPattern("[a-z&&[^m-p]]+", "^[a-z&&[^m-p]]+$", ImmutableList.of("a", "z", "az"), + ImmutableList.of("m", "anz", "o")); + testPattern("^[\\[-b&&[^^-a]]+$", "^\\^[\\[-b&&[^^-a]]+\\$$", ImmutableList.of("^[$", "^\\$", "^]$", "^b$"), + ImmutableList.of("^a$", "^^$", "^_$")); + + testPattern("[^^-~&&[^$-^]]", "^[^^-~&&[^$-^]]$", ImmutableList.of("!", "\"", "#"), + ImmutableList.of("a", "A", "z", "Z", "$", "%", "^", "}")); + testPattern("\\\\\\[^[^^-~&&[^$-^]]", "^\\\\\\[\\^[^^-~&&[^$-^]]$", + ImmutableList.of("\\[^ ", "\\[^!", "\\[^\"", "\\[^#"), + ImmutableList.of("\\[^a", "\\[^A", "\\[^z", "\\[^Z", "\\[^$", "\\[^%", "\\[^^", "\\[^}")); + testPattern("^\\[^\\\\[^^-b&&[^\\[-\\]]]\\]^", "^\\^\\[\\^\\\\[^^-b&&[^\\[-\\]]]\\]\\^$", + ImmutableList.of("^[^\\c]^", "^[^\\Z]^"), + ImmutableList.of("^[^\\[]^", "^[^\\\\]^", "^[^\\]]^", "^[^\\^]^", "^[^\\_]^", "^[^\\b]^")); + testPattern("[\\^]$", "^[\\^]\\$$", ImmutableList.of("^$"), + ImmutableList.of("^", "$", "$^", "\\", "\\^", "\\^\\", "\\^\\$")); + } + + @Test + public void testInvalidXSDRegexes() throws UnsupportedEncodingException { + testInvalidPattern("$^a^[$^\\]", "Unclosed character class"); + testInvalidPattern("$(\\)", "Unclosed group"); + } + + @Test + public void testJavaPattern() { + testPattern("^[$^]+$", ImmutableList.of("$^", "^", "$"), ImmutableList.of("\\", "a")); + testPattern("^[^$-^]$", ImmutableList.of("a", "_", "#"), ImmutableList.of("%", "^", "$", "]", "\\")); + } + + @Test + public void testYangPattern() throws Exception { + final SchemaContext context = StmtTestUtils.parseYangSources("/bugs/bug5410"); + assertNotNull(context); + + final PatternConstraint pattern = getPatternConstraintOf(context, "leaf-with-pattern"); + + final String rawRegex = pattern.getRawRegularExpression(); + final String expectedYangRegex = "$0$.*|$1$[a-zA-Z0-9./]{1,8}$[a-zA-Z0-9./]{22}|$5$(rounds=\\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{43}|$6$(rounds=\\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{86}"; + assertEquals(expectedYangRegex, rawRegex); + + final String javaRegexFromYang = pattern.getRegularExpression(); + final String expectedJavaRegex = "^\\$0\\$.*|\\$1\\$[a-zA-Z0-9./]{1,8}\\$[a-zA-Z0-9./]{22}|\\$5\\$(rounds=\\d+\\$)?[a-zA-Z0-9./]{1,16}\\$[a-zA-Z0-9./]{43}|\\$6\\$(rounds=\\d+\\$)?[a-zA-Z0-9./]{1,16}\\$[a-zA-Z0-9./]{86}$"; + assertEquals(expectedJavaRegex, javaRegexFromYang); + + final String value = "$6$AnrKGc0V$B/0/A.pWg4HrrA6YiEJOtFGibQ9Fmm5.4rI/00gEz3QeB7joSxBU3YtbHDm6NSkS1dKTQy3BWhwKKDS8nB5S//"; + testPattern(javaRegexFromYang, ImmutableList.of(value), ImmutableList.of()); + } + + @Test + public void testCaret() { + testPattern("^", "\\^"); + } + + @Test + public void testTextCaret() { + testPattern("abc^", "abc\\^"); + } + + @Test + public void testTextDollar() { + testPattern("abc$", "abc\\$"); + } + + @Test + public void testCaretCaret() { + testPattern("^^", "\\^\\^"); + } + + @Test + public void testCaretDollar() { + testPattern("^$", "\\^\\$"); + } + + @Test + public void testDot() { + testPattern(".", "."); + } + + @Test + public void testNotColon() { + testPattern("[^:]+", "[^:]+"); + } + + @Test + public void testDollar() { + testPattern("$", "\\$"); + } + + @Test + public void testDollarOneDollar() { + testPattern("$1$", "\\$1\\$"); + } + + @Test + public void testDollarPercentRange() { + testPattern("[$-%]+", "[$-%]+"); + } + + @Test + public void testDollarRange() { + testPattern("[$$]+", "[$$]+"); + } + + @Test + public void testDollarCaretRange() { + testPattern("[$^]+", "[$^]+"); + } + + @Test + public void testSimple() { + testPattern("abc", "abc"); + } + + @Test + public void testDotPlus() { + testPattern(".+", ".+"); + } + + @Test + public void testDotStar() { + testPattern(".*", ".*"); + } + + @Test + public void testSimpleOptional() { + testPattern("a?", "a?"); + } + + @Test + public void testRangeOptional() { + testPattern("[a-z]?", "[a-z]?"); + } + + private static void testPattern(final String xsdRegex, final String expectedJavaRegex, + final List positiveMatches, final List negativeMatches) { + final String javaRegexFromXSD = javaRegexFromXSD(xsdRegex); + assertEquals(expectedJavaRegex, javaRegexFromXSD); + + for (final String value : positiveMatches) { + assertTrue("Value '" + value + "' does not match java regex '" + javaRegexFromXSD + "'", + testMatch(javaRegexFromXSD, value)); + } + for (final String value : negativeMatches) { + assertFalse("Value '" + value + "' matches java regex '" + javaRegexFromXSD + "'", + testMatch(javaRegexFromXSD, value)); + } + } + + private static void testPattern(final String javaRegex, final List positiveMatches, + final List negativeMatches) { + for (final String value : positiveMatches) { + assertTrue("Value '" + value + "' does not match java regex '" + javaRegex + "'", + testMatch(javaRegex, value)); + } + for (final String value : negativeMatches) { + assertFalse("Value '" + value + "' matches java regex '" + javaRegex + "'", testMatch(javaRegex, value)); + } + } + + private static String javaRegexFromXSD(final String xsdRegex) { + return PatternStatementImpl.Definition.getJavaRegexFromXSD(xsdRegex); + } + + private static boolean testMatch(final String javaRegex, final String value) { + return value.matches(javaRegex); + } + + private static void testPattern(final String xsdRegex, final String unanchoredJavaRegex) { + testPattern(xsdRegex, '^' + unanchoredJavaRegex + '$', ImmutableList.of(), ImmutableList.of()); + } + + private static PatternConstraint getPatternConstraintOf(final SchemaContext context, final String leafName) { + final DataSchemaNode dataChildByName = context.getDataChildByName(foo(leafName)); + assertTrue(dataChildByName instanceof LeafSchemaNode); + final LeafSchemaNode leaf = (LeafSchemaNode) dataChildByName; + final TypeDefinition> type = leaf.getType(); + assertTrue(type instanceof StringTypeDefinition); + final StringTypeDefinition strType = (StringTypeDefinition) type; + return strType.getPatternConstraints().iterator().next(); + } + + private static QName foo(final String localName) { + return QName.create(FOO_NS, FOO_REV, localName); + } + + private static void testInvalidPattern(final String xsdRegex, final String expectedMessage) throws UnsupportedEncodingException { + final PrintStream stdout = System.out; + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + System.setOut(new PrintStream(output, true, "UTF-8")); + + javaRegexFromXSD(xsdRegex); + + final String testLog = output.toString(); + assertTrue(testLog.contains(expectedMessage)); + System.setOut(stdout); + } +} diff --git a/yang/yang-parser-impl/src/test/resources/bugs/bug5410/foo.yang b/yang/yang-parser-impl/src/test/resources/bugs/bug5410/foo.yang new file mode 100644 index 0000000000..8c47a131b4 --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/bugs/bug5410/foo.yang @@ -0,0 +1,10 @@ +module foo { + namespace foo; + prefix foo; + + leaf leaf-with-pattern { + type string { + pattern "$0$.*|$1$[a-zA-Z0-9./]{1,8}$[a-zA-Z0-9./]{22}|$5$(rounds=\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{43}|$6$(rounds=\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{86}"; + } + } +} -- 2.36.6