From 65f6914c3cf204b07a8fba778e2c6955fcbf3a50 Mon Sep 17 00:00:00 2001 From: "Andreas Penski (init)" Date: Fri, 2 Oct 2020 09:47:28 +0200 Subject: [PATCH] https://github.com/itplr-kosit/validator/issues/57 fix read stream --- pom.xml | 3 +- .../validationtool/daemon/CheckHandler.java | 30 +++++++++++++--- .../impl/input/StreamHelper.java | 36 +++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 2fc00c3..5481c7f 100644 --- a/pom.xml +++ b/pom.xml @@ -507,7 +507,7 @@ net.revelc.code.formatter formatter-maven-plugin - 2.12.2 + 2.13.0 validate @@ -519,6 +519,7 @@ ${project.basedir}/formatter.xml + LF diff --git a/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java b/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java index ff7cb5b..96073e1 100644 --- a/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java +++ b/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java @@ -1,11 +1,13 @@ package de.kosit.validationtool.daemon; +import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.util.concurrent.atomic.AtomicLong; +import org.apache.commons.lang3.StringUtils; + import com.sun.net.httpserver.HttpExchange; import lombok.RequiredArgsConstructor; @@ -15,6 +17,7 @@ import de.kosit.validationtool.api.Check; import de.kosit.validationtool.api.InputFactory; import de.kosit.validationtool.api.Result; import de.kosit.validationtool.impl.input.SourceInput; +import de.kosit.validationtool.impl.input.StreamHelper; import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; @@ -47,9 +50,9 @@ class CheckHandler extends BaseHandler { final String requestMethod = httpExchange.getRequestMethod(); // check neccessary, since gui can be disabled if (requestMethod.equals("POST")) { - final InputStream inputStream = httpExchange.getRequestBody(); - if (inputStream.available() > 0) { - final SourceInput serverInput = (SourceInput) InputFactory.read(inputStream, + final BufferedInputStream buffered = StreamHelper.wrapPeekable(httpExchange.getRequestBody()); + if (!isMultipartFormData(httpExchange) && isContentAvailable(httpExchange, buffered)) { + final SourceInput serverInput = (SourceInput) InputFactory.read(buffered, resolveInputName(httpExchange.getRequestURI())); final Result result = this.implemenation.checkInput(serverInput); write(httpExchange, serialize(result), APPLICATION_XML, resolveStatus(result)); @@ -61,10 +64,29 @@ class CheckHandler extends BaseHandler { error(httpExchange, HttpStatus.SC_METHOD_NOT_ALLOWED, "Method not supported"); } } catch (final Exception e) { + log.error("Error checking entity", e); error(httpExchange, HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal error: " + e.getMessage()); } } + private static boolean isContentAvailable(final com.sun.net.httpserver.HttpExchange httpExchange, final BufferedInputStream buffered) + throws IOException { + final String length = httpExchange.getRequestHeaders().getFirst("Content-length"); + if (StringUtils.isNumeric(length)) { + return Integer.parseInt(length) > 0; + } + return streamContainsContent(buffered); + } + + private static boolean isMultipartFormData(final HttpExchange httpExchange) { + return httpExchange.getRequestHeaders().getFirst("Content-type").startsWith("multipart"); + } + + private static boolean streamContainsContent(final BufferedInputStream requestBody) throws IOException { + return requestBody.available() > 0; + + } + private static String resolveInputName(final URI requestURI) { final String path = requestURI.getPath(); if (path.equalsIgnoreCase("/")) { diff --git a/src/main/java/de/kosit/validationtool/impl/input/StreamHelper.java b/src/main/java/de/kosit/validationtool/impl/input/StreamHelper.java index ad94155..62604a2 100644 --- a/src/main/java/de/kosit/validationtool/impl/input/StreamHelper.java +++ b/src/main/java/de/kosit/validationtool/impl/input/StreamHelper.java @@ -1,5 +1,6 @@ package de.kosit.validationtool.impl.input; +import java.io.BufferedInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -45,6 +46,35 @@ public class StreamHelper { } + private static class PeekableInputStream extends BufferedInputStream { + + public PeekableInputStream(final InputStream in) { + super(in); + } + + @Override + public synchronized int available() throws IOException { + int count = super.available(); + if (count == 0) { + count = peek(); + } + return count; + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private int peek() throws IOException { + try { + mark(2); + read(); + read(); + reset(); + } catch (final IOException e) { + return 0; + } + return super.available(); + } + } + @SuppressWarnings("squid:S4929") // efficient read is done by internally used stream private static class CountInputStream extends FilterInputStream { @@ -103,6 +133,10 @@ public class StreamHelper { return new DigestingInputStream(input, stream, createDigest(digestAlgorithm)); } + public static BufferedInputStream wrapPeekable(final InputStream stream) { + return new PeekableInputStream(stream); + } + /** * Drains the {@link Input} without further processing. This is useful to computing hashcode etc. * @@ -129,8 +163,10 @@ public class StreamHelper { public static void drain(final InputStream input) throws IOException { final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + // noinspection unused int n; + // noinspection StatementWithEmptyBody,UnusedAssignment while (EOF != (n = input.read(buffer))) { // nothing }