Bug 5911 - Restconf draft11 - bad parsing
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / restconf / parser / builder / YangInstanceIdentifierDeserializer.java
index 30b35486158bb11d18ec8faea4d5018d47c6ba20..6fc16fa232a78a18b0b2e4d0a2c87addb79e3fe5 100644 (file)
@@ -11,8 +11,11 @@ import com.google.common.base.CharMatcher;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import org.opendaylight.netconf.md.sal.rest.common.RestconfValidationUtils;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
 import org.opendaylight.restconf.utils.RestconfConstants;
 import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants;
 import org.opendaylight.restconf.utils.schema.context.RestconfSchemaUtil;
@@ -50,31 +53,34 @@ public final class YangInstanceIdentifierDeserializer {
      */
     public static Iterable<PathArgument> create(final SchemaContext schemaContext, final String data) {
         final List<PathArgument> path = new LinkedList<>();
-        DataSchemaContextNode<?> current = DataSchemaContextTree.from(schemaContext).getRoot();
-        final int offset = 0;
-        final MainVarsWrapper variables = new YangInstanceIdentifierDeserializer.MainVarsWrapper(data,
-                current, offset, schemaContext);
-
-        while (!allCharsConsumed(variables)) {
-            validArg(variables);
-            final QName qname = prepareQName(variables);
-
-            // this is the last identifier (input is consumed) or end of identifier (slash)
-            if (allCharsConsumed(variables)
-                    || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) {
-                prepareIdentifier(qname, path, variables);
-                path.add(variables.getCurrent().getIdentifier());
-            } else if (currentChar(variables.getOffset(),
-                    variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
-                current = nextContextNode(qname, path, variables);
-                if (!current.isKeyedEntry()) {
-                    prepareNodeWithValue(qname, path, variables);
+        final MainVarsWrapper variables = new YangInstanceIdentifierDeserializer.MainVarsWrapper(
+                data, DataSchemaContextTree.from(schemaContext).getRoot(),
+                YangInstanceIdentifierDeserializer.MainVarsWrapper.STARTING_OFFSET, schemaContext);
+
+        checkValid(!data.isEmpty(), "Empty path is not valid", variables.getData(), variables.getOffset());
+
+        if (!data.equals(String.valueOf(RestconfConstants.SLASH))) {
+            while (!allCharsConsumed(variables)) {
+                validArg(variables);
+                final QName qname = prepareQName(variables);
+
+                // this is the last identifier (input is consumed) or end of identifier (slash)
+                if (allCharsConsumed(variables)
+                        || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) {
+                    prepareIdentifier(qname, path, variables);
+                    path.add(variables.getCurrent().getIdentifier());
+                } else if (currentChar(variables.getOffset(),
+                        variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
+                    if (nextContextNode(qname, path, variables).getDataSchemaNode() instanceof ListSchemaNode) {
+                        prepareNodeWithPredicates(qname, path, variables);
+                    } else {
+                        prepareNodeWithValue(qname, path, variables);
+                    }
                 } else {
-                    prepareNodeWithPredicates(qname, path, variables);
+                    throw new IllegalArgumentException(
+                            "Bad char " + currentChar(variables.getOffset(), variables.getData()) + " on position "
+                                    + variables.getOffset() + ".");
                 }
-            } else {
-                throw new IllegalArgumentException(
-                        "Bad char " + currentChar(offset, data) + " on position " + offset + ".");
             }
         }
 
@@ -82,36 +88,85 @@ public final class YangInstanceIdentifierDeserializer {
     }
 
     private static void prepareNodeWithPredicates(final QName qname, final List<PathArgument> path,
-            final MainVarsWrapper variables) {
-        final List<QName> keyDefinitions = ((ListSchemaNode) variables.getCurrent().getDataSchemaNode())
-                .getKeyDefinition();
-        final ImmutableMap.Builder<QName, Object> keyValues = ImmutableMap.builder();
-
-        for (final QName keyQName : keyDefinitions) {
-            skipCurrentChar(variables);
-            String value = null;
-            if ((currentChar(variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA)
-                    || (currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH)) {
-                value = ParserBuilderConstants.Deserializer.EMPTY_STRING;
-            } else {
-                value = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE,
-                        variables);
+                                                  final MainVarsWrapper variables) {
+
+        final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
+        checkValid((dataSchemaNode != null), "Data schema node is null", variables.getData(), variables.getOffset());
+
+        final Iterator<QName> keys = ((ListSchemaNode) dataSchemaNode).getKeyDefinition().iterator();
+        final ImmutableMap.Builder<QName, Object> values = ImmutableMap.builder();
+
+        // skip already expected equal sign
+        skipCurrentChar(variables);
+
+        // read key value separated by comma
+        while (keys.hasNext() && !allCharsConsumed(variables) && currentChar(variables.getOffset(),
+                variables.getData()) != RestconfConstants.SLASH) {
+
+            // empty key value
+            if (currentChar(variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA) {
+                values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
+                skipCurrentChar(variables);
+                continue;
+            }
+
+            // check if next value is parsable
+            RestconfValidationUtils.checkDocumentedError(
+                    ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE
+                            .matches(currentChar(variables.getOffset(), variables.getData())),
+                    RestconfError.ErrorType.PROTOCOL,
+                    RestconfError.ErrorTag.MALFORMED_MESSAGE,
+                    ""
+            );
+
+            // parse value
+            values.put(keys.next(), findAndParsePercentEncoded(nextIdentifierFromNextSequence(
+                    ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables)));
+
+            // skip comma
+            if (keys.hasNext() && !allCharsConsumed(variables) && currentChar(
+                    variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA) {
+                skipCurrentChar(variables);
+            }
+        }
+
+        // the last key is considered to be empty
+        if (keys.hasNext()) {
+            if (allCharsConsumed(variables)
+                    || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) {
+                values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
             }
-            value = findAndParsePercentEncoded(value);
-            keyValues.put(keyQName, value);
+
+            // there should be no more missing keys
+            RestconfValidationUtils.checkDocumentedError(
+                    !keys.hasNext(),
+                    RestconfError.ErrorType.PROTOCOL,
+                    RestconfError.ErrorTag.MISSING_ATTRIBUTE,
+                    "Key value missing for: " + qname
+            );
         }
-        path.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qname, keyValues.build()));
+
+        path.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qname, values.build()));
     }
 
+
     private static QName prepareQName(final MainVarsWrapper variables) {
         checkValid(
                 ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR
                         .matches(currentChar(variables.getOffset(), variables.getData())),
                 "Identifier must start with character from set 'a-zA-Z_'", variables.getData(), variables.getOffset());
-        final String preparedPrefix = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
+        final String preparedPrefix = nextIdentifierFromNextSequence(
+                ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
         final String prefix, localName;
 
+        if (allCharsConsumed(variables)) {
+            return getQNameOfDataSchemaNode(preparedPrefix, variables);
+        }
+
         switch (currentChar(variables.getOffset(), variables.getData())) {
+            case RestconfConstants.SLASH:
+                prefix = preparedPrefix;
+                return getQNameOfDataSchemaNode(prefix, variables);
             case ParserBuilderConstants.Deserializer.COLON:
                 prefix = preparedPrefix;
                 skipCurrentChar(variables);
@@ -122,10 +177,14 @@ public final class YangInstanceIdentifierDeserializer {
                         variables.getOffset());
                 localName = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
 
-                final Module module = moduleForPrefix(prefix, variables.getSchemaContext());
-                Preconditions.checkArgument(module != null, "Failed to lookup prefix %s", prefix);
-
-                return QName.create(module.getQNameModule(), localName);
+                if (!allCharsConsumed(variables) && currentChar
+                        (variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
+                    return getQNameOfDataSchemaNode(localName, variables);
+                } else {
+                    final Module module = moduleForPrefix(prefix, variables.getSchemaContext());
+                    Preconditions.checkArgument(module != null, "Failed to lookup prefix %s", prefix);
+                    return QName.create(module.getQNameModule(), localName);
+                }
             case ParserBuilderConstants.Deserializer.EQUAL:
                 prefix = preparedPrefix;
                 return getQNameOfDataSchemaNode(prefix, variables);
@@ -150,9 +209,18 @@ public final class YangInstanceIdentifierDeserializer {
     private static void prepareNodeWithValue(final QName qname, final List<PathArgument> path,
             final MainVarsWrapper variables) {
         skipCurrentChar(variables);
-        String value = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
-        value = findAndParsePercentEncoded(value);
-        path.add(new YangInstanceIdentifier.NodeWithValue<>(qname, value));
+        final String value = nextIdentifierFromNextSequence(
+                ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables);
+
+        // exception if value attribute is missing
+        RestconfValidationUtils.checkDocumentedError(
+                !value.isEmpty(),
+                RestconfError.ErrorType.PROTOCOL,
+                RestconfError.ErrorTag.MISSING_ATTRIBUTE,
+                "Value missing for: " + qname
+        );
+
+        path.add(new YangInstanceIdentifier.NodeWithValue<>(qname, findAndParsePercentEncoded(value)));
     }
 
     private static void prepareIdentifier(final QName qname, final List<PathArgument> path,
@@ -176,26 +244,26 @@ public final class YangInstanceIdentifierDeserializer {
         return current;
     }
 
-    private static String findAndParsePercentEncoded(String preparedPrefix) {
+    private static String findAndParsePercentEncoded(final String preparedPrefix) {
         if (!preparedPrefix.contains(String.valueOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING))) {
             return preparedPrefix;
         }
-        final StringBuilder newPrefix = new StringBuilder();
-        int i = 0;
-        int startPoint = 0;
-        int endPoint = preparedPrefix.length();
-        while ((i = preparedPrefix.indexOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING)) != -1) {
-            newPrefix.append(preparedPrefix.substring(startPoint, i));
-            startPoint = i;
-            startPoint++;
-            final String hex = preparedPrefix.substring(startPoint, startPoint + 2);
-            startPoint += 2;
-            newPrefix.append((char) Integer.parseInt(hex, 16));
-            preparedPrefix = preparedPrefix.substring(startPoint, endPoint);
-            startPoint = 0;
-            endPoint = preparedPrefix.length();
+
+        final StringBuilder parsedPrefix = new StringBuilder(preparedPrefix);
+        final CharMatcher matcher = CharMatcher.is(ParserBuilderConstants.Deserializer.PERCENT_ENCODING);
+
+        while (matcher.matchesAnyOf(parsedPrefix)) {
+            final int percentCharPosition = matcher.indexIn(parsedPrefix);
+            parsedPrefix.replace(
+                    percentCharPosition,
+                    percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR,
+                    String.valueOf((char) Integer.parseInt(parsedPrefix.substring(
+                            percentCharPosition + ParserBuilderConstants.Deserializer.FIRST_ENCODED_CHAR,
+                            percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR),
+                            ParserBuilderConstants.Deserializer.PERCENT_ENCODED_RADIX)));
         }
-        return newPrefix.toString();
+
+        return parsedPrefix.toString();
     }
 
     private static QName getQNameOfDataSchemaNode(final String nodeName, final MainVarsWrapper variables) {
@@ -244,7 +312,8 @@ public final class YangInstanceIdentifierDeserializer {
         return variables.getOffset() == variables.getData().length();
     }
 
-    private static class MainVarsWrapper {
+    private final static class MainVarsWrapper {
+        private static final int STARTING_OFFSET = 0;
 
         private final SchemaContext schemaContext;
         private final String data;
@@ -254,9 +323,9 @@ public final class YangInstanceIdentifierDeserializer {
         public MainVarsWrapper(final String data, final DataSchemaContextNode<?> current, final int offset,
                 final SchemaContext schemaContext) {
             this.data = data;
-            this.schemaContext = schemaContext;
             this.setCurrent(current);
             this.setOffset(offset);
+            this.schemaContext = schemaContext;
         }
 
         public String getData() {
@@ -282,6 +351,5 @@ public final class YangInstanceIdentifierDeserializer {
         public SchemaContext getSchemaContext() {
             return this.schemaContext;
         }
-
     }
 }