diff --git a/CHANGELOG.md b/CHANGELOG.md index 7564335c..d830d0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 1.9.4 (released 04.02.2022) +* #41 fix "IOException: mark/reset not supported" when parsing http parameters + ## 1.9.3 (released 03.02.2022) * upgrade to guice guice:5.1.0, hibernate-core:5.6.5.Final, commons-beanutils:1.9.4 etc. diff --git a/build.gradle b/build.gradle index 21bb26a3..58033302 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ defaultTasks 'clean', 'check', 'publishToMavenLocal' subprojects { group='com.codeborne.replay' - version=project.properties['revision'] ?: '1.9.3' + version=project.properties['revision'] ?: '1.9.4' apply plugin: 'java' apply plugin: 'java-library' diff --git a/framework/src/play/data/parsing/TextParser.java b/framework/src/play/data/parsing/TextParser.java index d3cf8499..e177d367 100644 --- a/framework/src/play/data/parsing/TextParser.java +++ b/framework/src/play/data/parsing/TextParser.java @@ -1,24 +1,39 @@ package play.data.parsing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import play.exceptions.UnexpectedException; import play.mvc.Http; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import static org.apache.commons.io.IOUtils.toByteArray; public class TextParser extends DataParser { + private static final Logger log = LoggerFactory.getLogger(TextParser.class); + @Override public Map parse(Http.Request request) { + Map params = new HashMap<>(); try { - Map params = new HashMap<>(); byte[] data = toByteArray(request.body); params.put("body", new String[] {new String(data, request.encoding)}); - request.body.reset(); - return params; - } catch (Exception e) { + } catch (IOException e) { throw new UnexpectedException(e); } + resetBodyInputStreamIfPossible(request); + return params; + } + + private void resetBodyInputStreamIfPossible(Http.Request request) { + try { + request.body.reset(); + } + catch (IOException resetNotSupported) { + log.warn("Failed to reset request.body of type {}: {}", + resetNotSupported.getClass().getName(), resetNotSupported.toString()); + } } } diff --git a/framework/src/play/server/FileChannelBuffer.java b/framework/src/play/server/FileChannelBuffer.java index f1b0a6ca..dba2b893 100644 --- a/framework/src/play/server/FileChannelBuffer.java +++ b/framework/src/play/server/FileChannelBuffer.java @@ -1,9 +1,17 @@ package play.server; -import org.jboss.netty.buffer.*; - -import java.io.*; +import org.jboss.netty.buffer.AbstractChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBufferFactory; +import org.jboss.netty.buffer.ChannelBufferIndexFinder; +import org.jboss.netty.buffer.WrappedChannelBuffer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.GatheringByteChannel; @@ -15,21 +23,12 @@ */ public class FileChannelBuffer extends AbstractChannelBuffer implements WrappedChannelBuffer { - private final FileInputStream is; - + private final InputStream is; - public FileChannelBuffer(File file) { - if (file == null) { - throw new NullPointerException("file"); - } - try { - this.is = new FileInputStream(file); - } catch (Exception e) { - throw new RuntimeException(e); - } + public FileChannelBuffer(File file) throws FileNotFoundException { + this.is = new ResettableFileInputStream(file); } - public InputStream getInputStream() { return is; } @@ -113,16 +112,13 @@ public void setLong(int index, long value) { } @Override - public int setBytes(int index, InputStream in, int length) - throws IOException { + public int setBytes(int index, InputStream in, int length) { throw new RuntimeException(); } @Override - public int setBytes(int index, ScatteringByteChannel in, int length) - throws IOException { + public int setBytes(int index, ScatteringByteChannel in, int length) { throw new RuntimeException(); - } @Override diff --git a/framework/src/play/server/ResettableFileInputStream.java b/framework/src/play/server/ResettableFileInputStream.java new file mode 100644 index 00000000..a06fe861 --- /dev/null +++ b/framework/src/play/server/ResettableFileInputStream.java @@ -0,0 +1,34 @@ +package play.server; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import static java.util.Objects.requireNonNull; + +class ResettableFileInputStream extends InputStream { + private final File file; + private InputStream in; + + ResettableFileInputStream(File file) throws FileNotFoundException { + this.file = requireNonNull(file); + reset(); + } + + @Override + public int read() throws IOException { + return in.read(); + } + + @Override + public final synchronized void reset() throws FileNotFoundException { + in = new FileInputStream(file); + } + + @Override + public void close() throws IOException { + in.close(); + } +} diff --git a/framework/test/play/data/parsing/TextParserTest.java b/framework/test/play/data/parsing/TextParserTest.java index 2b640d11..81f17d27 100644 --- a/framework/test/play/data/parsing/TextParserTest.java +++ b/framework/test/play/data/parsing/TextParserTest.java @@ -1,23 +1,26 @@ package play.data.parsing; +import org.apache.commons.io.FileUtils; import org.junit.Test; import play.mvc.Http; +import play.server.FileChannelBuffer; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.commons.io.IOUtils.toByteArray; import static org.assertj.core.api.Assertions.assertThat; public class TextParserTest { - TextParser parser = new TextParser(); + private final TextParser parser = new TextParser(); @Test public void parsesRequestBodyAsText() throws IOException { - Http.Request request = new Http.Request(); - request.encoding = UTF_8; - request.body = new ByteArrayInputStream("Don't reset me please".getBytes(UTF_8)); + Http.Request request = givenRequest(new ByteArrayInputStream("Don't reset me please".getBytes(UTF_8))); assertThat(parser.parse(request).get("body")) .as("returns request body as <'body', body> map") @@ -27,4 +30,33 @@ public void parsesRequestBodyAsText() throws IOException { .as("Important: request body should not be reset - some controllers might need to read it") .isEqualTo("Don't reset me please".getBytes(UTF_8)); } + + @Test + public void fileChannelBuffer_supports_reset() throws IOException { + File tempFile = File.createTempFile("replay", "test"); + FileUtils.write(tempFile, "Don't reset me please", UTF_8); + + Http.Request request = givenRequest(new FileChannelBuffer(tempFile).getInputStream()); + + assertThat(parser.parse(request).get("body")).isEqualTo(new String[] {"Don't reset me please"}); + assertThat(toByteArray(request.body)).isEqualTo("Don't reset me please".getBytes(UTF_8)); + } + + @Test + public void shouldNotFail_ifInputStreamDoesNotSupportReset() throws IOException { + File tempFile = File.createTempFile("replay", "test"); + FileUtils.write(tempFile, "Don't reset me please", UTF_8); + + Http.Request request = givenRequest(new FileInputStream(tempFile)); + + assertThat(parser.parse(request).get("body")).isEqualTo(new String[] {"Don't reset me please"}); + assertThat(toByteArray(request.body)).isEqualTo("".getBytes(UTF_8)); + } + + private Http.Request givenRequest(InputStream in) { + Http.Request request = new Http.Request(); + request.encoding = UTF_8; + request.body = in; + return request; + } } \ No newline at end of file diff --git a/framework/test/play/server/ResettableFileInputStreamTest.java b/framework/test/play/server/ResettableFileInputStreamTest.java new file mode 100644 index 00000000..d6f0ae60 --- /dev/null +++ b/framework/test/play/server/ResettableFileInputStreamTest.java @@ -0,0 +1,27 @@ +package play.server; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +public class ResettableFileInputStreamTest { + @Test + public void canResetInputStream() throws IOException { + File file = File.createTempFile("replay-resettable-file-input-stream", "test"); + FileUtils.write(file, "zebra", UTF_8); + + ResettableFileInputStream in = new ResettableFileInputStream(file); + + assertThat(IOUtils.toString(in, UTF_8)).isEqualTo("zebra"); + assertThat(IOUtils.toString(in, UTF_8)).isEqualTo(""); + + in.reset(); + assertThat(IOUtils.toString(in, UTF_8)).isEqualTo("zebra"); + } +} \ No newline at end of file