Bug 8988 - Check for empty payload properly 55/63055/4
authorJakub Morvay <jmorvay@cisco.com>
Tue, 12 Sep 2017 14:48:23 +0000 (16:48 +0200)
committerJakub Morvay <jmorvay@cisco.com>
Wed, 20 Sep 2017 09:57:29 +0000 (11:57 +0200)
Do not use InputStream#avalaible method to check for empty payload's
input streams in our implementations of MessageBodyReader interface.
InputStream#avalaible method returns an estimate of the number of bytes
that can be read from input stream without blocking. We cannot be sure
the stream is actually empty, even if this method returns 0.

Wrap the payload's input stream in PushbackInputStream and try to read
and unread from it instead.

Change-Id: I43b3a8d837f3dc4bc59d7cc2db29ffa06786844f
Signed-off-by: Jakub Morvay <jmorvay@cisco.com>
restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/RestUtil.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/spi/AbstractIdentifierAwareJaxRsProvider.java

index 9deb2ca0329ed30416db6f913d6d9dfdb4013294..ff04373f2c0ad9de09a0979ecc428dee4100e1ce 100644 (file)
@@ -7,8 +7,13 @@
  */
 package org.opendaylight.restconf.common.util;
 
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.xml.stream.events.StartElement;
@@ -32,6 +37,30 @@ public final class RestUtil {
         return superType;
     }
 
+    /**
+     * Utility method to find out if is provided {@link InputStream} empty.
+     *
+     * @param  entityStream {@link InputStream} to be checked if it is empty.
+     * @return Empty Optional if provided input stream is empty or Optional
+     *         containing input stream otherwise.
+     *
+     * @throws IOException if an IO error arises during stream inspection.
+     * @throws NullPointerException if provided stream is null.
+     *
+     */
+    public static Optional<InputStream> isInputStreamEmpty(final InputStream entityStream) throws IOException {
+        Preconditions.checkNotNull(entityStream);
+        final PushbackInputStream pushbackInputStream = new PushbackInputStream(entityStream);
+
+        int firstByte = pushbackInputStream.read();
+        if (firstByte == -1) {
+            return Optional.empty();
+        } else {
+            pushbackInputStream.unread(firstByte);
+            return Optional.of(pushbackInputStream);
+        }
+    }
+
     public static IdentityValuesDTO asInstanceIdentifier(final String value, final PrefixesMaping prefixMap) {
         final String valueTrimmed = value.trim();
         if (!valueTrimmed.startsWith("/")) {
index f02de61459e8ef50bf10b9347bcdf60b88380f02..4ce6997aa0efb38d4fa3f53ad5efcae2fff57702 100644 (file)
@@ -16,6 +16,7 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
@@ -30,6 +31,7 @@ import org.opendaylight.restconf.common.context.NormalizedNodeContext;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.util.RestUtil;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
@@ -92,7 +94,8 @@ public class JsonNormalizedNodeBodyReader
     private static NormalizedNodeContext readFrom(final InstanceIdentifierContext<?> path,
                                                   final InputStream entityStream, final boolean isPost)
             throws IOException {
-        if (entityStream.available() < 1) {
+        final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+        if (!nonEmptyInputStreamOptional.isPresent()) {
             return new NormalizedNodeContext(path, null);
         }
         final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
@@ -114,7 +117,7 @@ public class JsonNormalizedNodeBodyReader
         }
 
         final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(), parentSchema);
-        final JsonReader reader = new JsonReader(new InputStreamReader(entityStream));
+        final JsonReader reader = new JsonReader(new InputStreamReader(nonEmptyInputStreamOptional.get()));
         jsonParser.parse(reader);
 
         NormalizedNode<?, ?> result = resultHolder.getResult();
index da36eefeb169b65ac472f8e6c35dbe6585d58025..d902f3648b2f92105477a5333a9972b14773f507 100644 (file)
@@ -19,6 +19,7 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import javax.annotation.Nonnull;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
@@ -36,6 +37,7 @@ import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
 import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.common.patch.PatchEditOperation;
 import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.util.RestUtil;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
@@ -95,11 +97,12 @@ public class JsonToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider
 
     private PatchContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream)
             throws IOException {
-        if (entityStream.available() < 1) {
+        final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+        if (!nonEmptyInputStreamOptional.isPresent()) {
             return new PatchContext(path, null, null);
         }
 
-        final JsonReader jsonReader = new JsonReader(new InputStreamReader(entityStream));
+        final JsonReader jsonReader = new JsonReader(new InputStreamReader(nonEmptyInputStreamOptional.get()));
         final List<PatchEntity> resultList = read(jsonReader, path);
         jsonReader.close();
 
index e4f114f0ea327fdd79583936888a62761838ca07..4c2eae4992aaabed1f6ff2e239b2d3f7f2038d42 100644 (file)
@@ -19,6 +19,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Deque;
 import java.util.List;
+import java.util.Optional;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
@@ -35,6 +36,7 @@ import org.opendaylight.restconf.common.context.NormalizedNodeContext;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.util.RestUtil;
 import org.opendaylight.yangtools.util.xml.UntrustedXML;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -96,13 +98,13 @@ public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsPro
     private NormalizedNodeContext readFrom(final InputStream entityStream) throws IOException, SAXException,
             XMLStreamException, ParserConfigurationException, URISyntaxException {
         final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
-
-        if (entityStream.available() < 1) {
+        final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+        if (!nonEmptyInputStreamOptional.isPresent()) {
             // represent empty nopayload input
             return new NormalizedNodeContext(path, null);
         }
 
-        final Document doc = UntrustedXML.newDocumentBuilder().parse(entityStream);
+        final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
         return parse(path, doc);
     }
 
index 56e3733fa026eaad72f2abf3e8d436b2deeb14f5..c81c770e8c5f63014800980c3ee2a072187cf3af 100644 (file)
@@ -19,6 +19,7 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Optional;
 import javax.annotation.Nonnull;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
@@ -38,6 +39,7 @@ import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
 import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.common.patch.PatchEditOperation;
 import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.util.RestUtil;
 import org.opendaylight.yangtools.util.xml.UntrustedXML;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -90,13 +92,13 @@ public class XmlToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider i
 
         try {
             final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
-
-            if (entityStream.available() < 1) {
+            final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+            if (!nonEmptyInputStreamOptional.isPresent()) {
                 // represent empty nopayload input
                 return new PatchContext(path, null, null);
             }
 
-            final Document doc = UntrustedXML.newDocumentBuilder().parse(entityStream);
+            final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
             return parse(path, doc);
         } catch (final RestconfDocumentedException e) {
             throw e;
index ac87d2a1e84cd89cb3286120568fe224f0aaa31d..ab40b3274a7197b7dd7bc4261f8d4d0a8fe9db71 100644 (file)
@@ -11,6 +11,7 @@ package org.opendaylight.restconf.nb.rfc8040.jersey.providers.spi;
 import com.google.common.base.Optional;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PushbackInputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import javax.ws.rs.HttpMethod;
@@ -47,11 +48,17 @@ public abstract class AbstractIdentifierAwareJaxRsProvider<T> implements Message
             final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws IOException,
             WebApplicationException {
         final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
-        if (entityStream.available() < 1) {
+
+        final PushbackInputStream pushbackInputStream = new PushbackInputStream(entityStream);
+
+        int firstByte = pushbackInputStream.read();
+        if (firstByte == -1) {
             return emptyBody(path);
+        } else {
+            pushbackInputStream.unread(firstByte);
+            return readBody(path, pushbackInputStream);
         }
 
-        return readBody(path, entityStream);
     }
 
     /**