From cc6f3649ee1962e954b26ef686e92335f1637890 Mon Sep 17 00:00:00 2001 From: "alessandro.gherardi" Date: Tue, 9 Jan 2018 19:14:06 -0700 Subject: [PATCH 1/7] First commit --- .../http/impl/io/ChunkedInputStream.java | 8 +++- .../impl/io/ContentLengthInputStream.java | 8 +++- .../hc/core5/http/io/EofInputStream.java | 40 +++++++++++++++++++ .../core5/http/io/EofSensorInputStream.java | 6 ++- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 httpcore5/src/main/java/org/apache/hc/core5/http/io/EofInputStream.java diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java index c7ba75bdc7..a34f2c9330 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java @@ -37,6 +37,7 @@ import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.TruncatedChunkException; import org.apache.hc.core5.http.config.H1Config; +import org.apache.hc.core5.http.io.EofInputStream; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; @@ -57,7 +58,7 @@ * @since 4.0 * */ -public class ChunkedInputStream extends InputStream { +public class ChunkedInputStream extends EofInputStream { private static final int CHUNK_LEN = 1; private static final int CHUNK_DATA = 2; @@ -324,4 +325,9 @@ public Header[] getFooters() { return this.footers.clone(); } + @Override + public boolean atEof() { + return eof; + } + } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java index b2edb15cee..f1f134436a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java @@ -32,6 +32,7 @@ import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.StreamClosedException; +import org.apache.hc.core5.http.io.EofInputStream; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.util.Args; @@ -51,7 +52,7 @@ * * @since 4.0 */ -public class ContentLengthInputStream extends InputStream { +public class ContentLengthInputStream extends EofInputStream { private static final int BUFFER_SIZE = 2048; @@ -223,4 +224,9 @@ public long skip(final long n) throws IOException { } return count; } + + @Override + public boolean atEof() { + return pos == contentLength; + } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofInputStream.java new file mode 100644 index 0000000000..b15768c727 --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofInputStream.java @@ -0,0 +1,40 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.core5.http.io; + +import java.io.InputStream; + +public abstract class EofInputStream extends InputStream { + + /** + * Tries to determine, without blocking, if the entire content of the stream has already been read + * @return true if the entire content of the stream has already been read, false of either + * the entire content has not been read or a determination cannot be made without potentially blocking + */ + public abstract boolean atEof(); +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java index a00da02509..621b4ff042 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java @@ -192,7 +192,7 @@ public void close() throws IOException { private void checkEOF(final int eof) throws IOException { final InputStream toCheckStream = wrappedStream; - if ((toCheckStream != null) && (eof < 0)) { + if ((toCheckStream != null) && (eof < 0 || atEof(toCheckStream))) { try { boolean scws = true; // should close wrapped stream? if (eofWatcher != null) { @@ -207,6 +207,10 @@ private void checkEOF(final int eof) throws IOException { } } + private static boolean atEof(InputStream toCheckStream) { + return (toCheckStream instanceof EofInputStream) && ((EofInputStream)toCheckStream).atEof(); + } + /** * Detects stream close and notifies the watcher. * There's not much to detect since this is called by {@link #close close}. From dbc8aea63788ff287c0caef28562cadca824ffa6 Mon Sep 17 00:00:00 2001 From: "alessandro.gherardi" Date: Tue, 9 Jan 2018 19:16:16 -0700 Subject: [PATCH 2/7] Comment --- .../main/java/org/apache/hc/core5/http/io/EofInputStream.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofInputStream.java index b15768c727..f01b4ab70f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofInputStream.java @@ -33,8 +33,8 @@ public abstract class EofInputStream extends InputStream { /** * Tries to determine, without blocking, if the entire content of the stream has already been read - * @return true if the entire content of the stream has already been read, false of either - * the entire content has not been read or a determination cannot be made without potentially blocking + * @return true if the entire content of the stream has already been read, false if either + * the entire content has not been read or a determination cannot be made without blocking */ public abstract boolean atEof(); } From 9ac845528585964f957077935d2fc318f236335a Mon Sep 17 00:00:00 2001 From: "alessandro.gherardi" Date: Tue, 9 Jan 2018 19:32:08 -0700 Subject: [PATCH 3/7] Static check --- .../java/org/apache/hc/core5/http/io/EofSensorInputStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java index 621b4ff042..52e1405273 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java @@ -207,7 +207,7 @@ private void checkEOF(final int eof) throws IOException { } } - private static boolean atEof(InputStream toCheckStream) { + private static boolean atEof(final InputStream toCheckStream) { return (toCheckStream instanceof EofInputStream) && ((EofInputStream)toCheckStream).atEof(); } From b149bd145a3e3e0e76adc3563cda5803ce2893c8 Mon Sep 17 00:00:00 2001 From: "alessandro.gherardi" Date: Sat, 13 Jan 2018 14:34:40 -0700 Subject: [PATCH 4/7] Unit tests Changes --- .../http/impl/io/ChunkedInputStream.java | 56 +++++++++- .../http/impl/io/SessionInputBufferImpl.java | 10 ++ .../hc/core5/http/io/SessionInputBuffer.java | 13 +++ .../core5/http/impl/io/TestChunkCoding.java | 49 +++++++++ .../impl/io/TestContentLengthInputStream.java | 31 ++++-- ...ofSensorInputStreamWithEofInputStream.java | 101 ++++++++++++++++++ 6 files changed, 250 insertions(+), 10 deletions(-) create mode 100644 httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStreamWithEofInputStream.java diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java index a34f2c9330..cb1211cd89 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.io.InputStream; +import org.apache.hc.core5.http.Chars; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; @@ -155,6 +156,7 @@ public int read() throws IOException { pos++; if (pos >= chunkSize) { state = CHUNK_CRLF; + consumeEndOfMessageIfCached(); } } return b; @@ -191,6 +193,7 @@ public int read (final byte[] b, final int off, final int len) throws IOExceptio pos += bytesRead; if (pos >= chunkSize) { state = CHUNK_CRLF; + consumeEndOfMessageIfCached(); } return bytesRead; } @@ -325,9 +328,60 @@ public Header[] getFooters() { return this.footers.clone(); } + private void consumeEndOfMessageIfCached() throws IOException { + if (eof || state != CHUNK_CRLF) { + return; + } + + // To avoid blocking, peek at the unread buffered bytes. + // All content has been read if the unread bytes consist of: + // - CR+LF + // - A chunk size of 0, followed by CR+LF + // - Zero or more footers followed by CR+LF + // - An empty line + int bufferedLen = buffer.getBufferedLen(); + if (bufferedLen < 2 || buffer.peekBuffered(0) != Chars.CR || buffer.peekBuffered(1) != Chars.LF) { + return; + } + + // check if the next buffered line contains all zeros + int cur = 2; + boolean foundZeros = false; + for (; cur < bufferedLen; cur++) { + if (buffer.peekBuffered(cur) == Chars.CR) { + break; + } else if (buffer.peekBuffered(cur) == '0') { + foundZeros = true; + } else { + return; + } + } + if (!foundZeros) { + return; + } + + // check if the remaining buffered data contains an empty line, + // skipping any footers + boolean foundEmptyLine = false; + for (; cur + 3 < bufferedLen; cur++) { + if (buffer.peekBuffered(cur) == Chars.CR && + buffer.peekBuffered(cur + 1) == Chars.LF && + buffer.peekBuffered(cur + 2) == Chars.CR && + buffer.peekBuffered(cur + 3) == Chars.LF) { + foundEmptyLine = true; + break; + } + } + if (foundEmptyLine == false) { + return; + } + + // Read the next chunk header and footers. The operation should not block + nextChunk(); + } + @Override public boolean atEof() { return eof; } - } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java index 5e8c03d130..8e0e9ffb0b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java @@ -384,4 +384,14 @@ public HttpTransportMetrics getMetrics() { return this.metrics; } + @Override + public int getBufferedLen() { + return bufferlen - bufferpos; + } + + @Override + public byte peekBuffered(int offset) { + return buffer[this.bufferpos + offset]; + } + } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java index fe68b77170..107af2932a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java @@ -144,4 +144,17 @@ public interface SessionInputBuffer { */ HttpTransportMetrics getMetrics(); + /** + * Returns the number of bytes stored in the session buffer + * @return the number of bytes in the session buffer + */ + int getBufferedLen(); + + /** + * Returns the byte stored at the given offset in the session buffer. + * The offset must be >= 0 and < getBufferedLen() - 1. + * @param offset in the buffer + * @return the buffered byte + */ + byte peekBuffered(int offset); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java index 04e007fd0c..7d8fefae21 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java @@ -67,8 +67,11 @@ public void testChunkedInputStreamLargeBuffer() throws IOException { while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } + Assert.assertTrue(in.atEof()); Assert.assertEquals(-1, in.read(buffer)); + Assert.assertTrue(in.atEof()); Assert.assertEquals(-1, in.read(buffer)); + Assert.assertTrue(in.atEof()); in.close(); @@ -97,8 +100,12 @@ public void testChunkedInputStreamSmallBuffer() throws IOException { while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } + Assert.assertTrue(in.atEof()); Assert.assertEquals(-1, in.read(buffer)); + Assert.assertTrue(in.atEof()); Assert.assertEquals(-1, in.read(buffer)); + Assert.assertTrue(in.atEof()); + in.close(); @@ -124,8 +131,12 @@ public void testChunkedInputStreamOneByteRead() throws IOException { Assert.assertEquals(i, ch); i++; } + Assert.assertTrue(in.atEof()); Assert.assertEquals(-1, in.read()); + Assert.assertTrue(in.atEof()); Assert.assertEquals(-1, in.read()); + Assert.assertTrue(in.atEof()); + in.close(); } @@ -507,5 +518,43 @@ public void testHugeChunk() throws IOException { Assert.assertEquals("01234567", result); } + @Test + public void testAtEof() throws IOException { + final SessionInputBuffer inbuffer = new SessionInputBufferImpl(128); + String chunkedInput = CHUNKED_INPUT + "\r\n"; + final ByteArrayInputStream inputStream = new ByteArrayInputStream(chunkedInput.getBytes(StandardCharsets.ISO_8859_1)); + final ChunkedInputStream in = new ChunkedInputStream(inbuffer, inputStream); + final byte[] buffer = new byte[64]; + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + int len; + len = in.read(buffer); + Assert.assertEquals(16, len); + Assert.assertFalse(in.atEof()); + out.write(buffer, 0, len); + + len = in.read(buffer); + Assert.assertEquals(5, len); + Assert.assertTrue(in.atEof()); + out.write(buffer, 0, len); + + Assert.assertEquals(-1, in.read(buffer)); + Assert.assertTrue(in.atEof()); + Assert.assertEquals(-1, in.read(buffer)); + Assert.assertTrue(in.atEof()); + + in.close(); + + final String result = new String(out.toByteArray(), StandardCharsets.ISO_8859_1); + Assert.assertEquals(result, CHUNKED_RESULT); + + final Header[] footers = in.getFooters(); + Assert.assertNotNull(footers); + Assert.assertEquals(2, footers.length); + Assert.assertEquals("Footer1", footers[0].getName()); + Assert.assertEquals("abcde", footers[0].getValue()); + Assert.assertEquals("Footer2", footers[1].getName()); + Assert.assertEquals("fghij", footers[1].getValue()); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java index f54155ec9a..198dfc320b 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java @@ -46,13 +46,15 @@ public void testBasics() throws IOException { final String s = "1234567890123456"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); final SessionInputBuffer inbuffer = new SessionInputBufferImpl(16); - final InputStream in = new ContentLengthInputStream(inbuffer, inputStream, 10L); + final ContentLengthInputStream in = new ContentLengthInputStream(inbuffer, inputStream, 10L); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final byte[] buffer = new byte[50]; int len = in.read(buffer, 0, 2); + Assert.assertFalse(in.atEof()); outputStream.write(buffer, 0, len); len = in.read(buffer); + Assert.assertTrue(in.atEof()); outputStream.write(buffer, 0, len); final String result = new String(outputStream.toByteArray(), StandardCharsets.ISO_8859_1); @@ -64,34 +66,44 @@ public void testBasics() throws IOException { public void testSkip() throws IOException { final ByteArrayInputStream inputStream1 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inbuffer1 = new SessionInputBufferImpl(16); - final InputStream in1 = new ContentLengthInputStream(inbuffer1, inputStream1, 10L); + final ContentLengthInputStream in1 = new ContentLengthInputStream(inbuffer1, inputStream1, 10L); Assert.assertEquals(10, in1.skip(10)); + Assert.assertTrue(in1.atEof()); Assert.assertTrue(in1.read() == -1); + Assert.assertTrue(in1.atEof()); in1.close(); final ByteArrayInputStream inputStream2 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inbuffer2 = new SessionInputBufferImpl(16); - final InputStream in2 = new ContentLengthInputStream(inbuffer2, inputStream2, 10L); + final ContentLengthInputStream in2 = new ContentLengthInputStream(inbuffer2, inputStream2, 10L); in2.read(); + Assert.assertFalse(in2.atEof()); Assert.assertEquals(9, in2.skip(10)); + Assert.assertTrue(in2.atEof()); Assert.assertTrue(in2.read() == -1); + Assert.assertTrue(in2.atEof()); in2.close(); final ByteArrayInputStream inputStream3 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inbuffer3 = new SessionInputBufferImpl(16); - final InputStream in3 = new ContentLengthInputStream(inbuffer3, inputStream3, 2L); + final ContentLengthInputStream in3 = new ContentLengthInputStream(inbuffer3, inputStream3, 2L); in3.read(); + Assert.assertFalse(in3.atEof()); in3.read(); + Assert.assertTrue(in3.atEof()); Assert.assertTrue(in3.skip(10) <= 0); Assert.assertTrue(in3.skip(-1) == 0); Assert.assertTrue(in3.read() == -1); + Assert.assertTrue(in3.atEof()); in3.close(); final ByteArrayInputStream inputStream4 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inbuffer4 = new SessionInputBufferImpl(16); - final InputStream in4 = new ContentLengthInputStream(inbuffer4, inputStream4, 10L); + final ContentLengthInputStream in4 = new ContentLengthInputStream(inbuffer4, inputStream4, 10L); Assert.assertEquals(5,in4.skip(5)); + Assert.assertFalse(in4.atEof()); Assert.assertEquals(5, in4.read(new byte[20])); + Assert.assertTrue(in4.atEof()); in4.close(); } @@ -139,24 +151,25 @@ public void testTruncatedContent() throws IOException { final String s = "1234567890123456"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); final SessionInputBuffer inbuffer = new SessionInputBufferImpl(16); - final InputStream in = new ContentLengthInputStream(inbuffer, inputStream, 32L); + final ContentLengthInputStream in = new ContentLengthInputStream(inbuffer, inputStream, 32L); final byte[] tmp = new byte[32]; final int byteRead = in.read(tmp); Assert.assertEquals(16, byteRead); + Assert.assertFalse(in.atEof()); try { in.read(tmp); - Assert.fail("ConnectionClosedException should have been closed"); + Assert.fail("ConnectionClosedException should have been thrown"); } catch (final ConnectionClosedException ex) { } try { in.read(); - Assert.fail("ConnectionClosedException should have been closed"); + Assert.fail("ConnectionClosedException should have been thrown"); } catch (final ConnectionClosedException ex) { } try { in.close(); - Assert.fail("ConnectionClosedException should have been closed"); + Assert.fail("ConnectionClosedException should have been thrown"); } catch (final ConnectionClosedException ex) { } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStreamWithEofInputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStreamWithEofInputStream.java new file mode 100644 index 0000000000..ad5a570703 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStreamWithEofInputStream.java @@ -0,0 +1,101 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.core5.http.io; + +import java.io.InputStream; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestEofSensorInputStreamWithEofInputStream { + + private EofInputStream instream; + private EofSensorWatcher eofwatcher; + private EofSensorInputStream eofstream; + + @Before + public void setup() throws Exception { + instream = Mockito.mock(EofInputStream.class); + eofwatcher = Mockito.mock(EofSensorWatcher.class); + eofstream = new EofSensorInputStream(instream, eofwatcher); + } + + @Test + public void testReadAtEof() throws Exception { + Mockito.when(instream.read()).thenReturn(0); + Mockito.when(instream.atEof()).thenReturn(true); + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); + + Assert.assertEquals(0, eofstream.read()); + Assert.assertNull(eofstream.getWrappedStream()); + Mockito.verify(instream, Mockito.times(1)).close(); + Mockito.verify(eofwatcher).eofDetected(instream); + } + + @Test + public void testReadNotAtEof() throws Exception { + Mockito.when(instream.read()).thenReturn(0); + Mockito.when(instream.atEof()).thenReturn(false); + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); + + Assert.assertEquals(0, eofstream.read()); + Assert.assertNotNull(eofstream.getWrappedStream()); + Mockito.verify(instream, Mockito.never()).close(); + Mockito.verify(eofwatcher, Mockito.never()).eofDetected(instream); + } + + @Test + public void testReadByteArrayAtEof() throws Exception { + Mockito.when(instream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt())) + .thenReturn(1); + Mockito.when(instream.atEof()).thenReturn(true); + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); + + final byte[] tmp = new byte[1]; + + Assert.assertEquals(1, eofstream.read(tmp)); + Assert.assertNull(eofstream.getWrappedStream()); + Mockito.verify(instream, Mockito.times(1)).close(); + Mockito.verify(eofwatcher).eofDetected(instream); + } + + @Test + public void testReadByteArrayNotAtEof() throws Exception { + Mockito.when(instream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt())) + .thenReturn(1); + Mockito.when(instream.atEof()).thenReturn(false); + + final byte[] tmp = new byte[1]; + + Assert.assertEquals(1, eofstream.read(tmp)); + Assert.assertNotNull(eofstream.getWrappedStream()); + Mockito.verify(instream, Mockito.never()).close(); + Mockito.verify(eofwatcher, Mockito.never()).eofDetected(instream); + } +} From e50ca562301feae5ab37e1fb557d5a34f0d0de5c Mon Sep 17 00:00:00 2001 From: "alessandro.gherardi" Date: Sun, 14 Jan 2018 16:44:40 -0700 Subject: [PATCH 5/7] Rename --- .../apache/hc/core5/http/impl/io/ChunkedInputStream.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java index cb1211cd89..eb0aced623 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java @@ -156,7 +156,7 @@ public int read() throws IOException { pos++; if (pos >= chunkSize) { state = CHUNK_CRLF; - consumeEndOfMessageIfCached(); + consumeEndOfMessageIfBuffered(); } } return b; @@ -193,7 +193,7 @@ public int read (final byte[] b, final int off, final int len) throws IOExceptio pos += bytesRead; if (pos >= chunkSize) { state = CHUNK_CRLF; - consumeEndOfMessageIfCached(); + consumeEndOfMessageIfBuffered(); } return bytesRead; } @@ -328,7 +328,7 @@ public Header[] getFooters() { return this.footers.clone(); } - private void consumeEndOfMessageIfCached() throws IOException { + private void consumeEndOfMessageIfBuffered() throws IOException { if (eof || state != CHUNK_CRLF) { return; } From b45ba31af29aa0706f544c37a0e15062ad67863b Mon Sep 17 00:00:00 2001 From: "alessandro.gherardi" Date: Mon, 15 Jan 2018 09:04:31 -0700 Subject: [PATCH 6/7] Checkstyle --- .../org/apache/hc/core5/http/impl/io/ChunkedInputStream.java | 2 +- .../apache/hc/core5/http/impl/io/SessionInputBufferImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java index eb0aced623..7bf951e49e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java @@ -339,7 +339,7 @@ private void consumeEndOfMessageIfBuffered() throws IOException { // - A chunk size of 0, followed by CR+LF // - Zero or more footers followed by CR+LF // - An empty line - int bufferedLen = buffer.getBufferedLen(); + final int bufferedLen = buffer.getBufferedLen(); if (bufferedLen < 2 || buffer.peekBuffered(0) != Chars.CR || buffer.peekBuffered(1) != Chars.LF) { return; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java index 8e0e9ffb0b..212302ecbf 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java @@ -390,7 +390,7 @@ public int getBufferedLen() { } @Override - public byte peekBuffered(int offset) { + public byte peekBuffered(final int offset) { return buffer[this.bufferpos + offset]; } From 7fdeacaaac5a330ade03c87ed874bbfe7113a059 Mon Sep 17 00:00:00 2001 From: "alessandro.gherardi" Date: Mon, 15 Jan 2018 09:52:55 -0700 Subject: [PATCH 7/7] More checkstyle --- .../hc/core5/http/impl/io/TestChunkCoding.java | 2 +- ...TestEofSensorInputStreamWithEofInputStream.java | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java index 7d8fefae21..24b1e937c7 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java @@ -521,7 +521,7 @@ public void testHugeChunk() throws IOException { @Test public void testAtEof() throws IOException { final SessionInputBuffer inbuffer = new SessionInputBufferImpl(128); - String chunkedInput = CHUNKED_INPUT + "\r\n"; + final String chunkedInput = CHUNKED_INPUT + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(chunkedInput.getBytes(StandardCharsets.ISO_8859_1)); final ChunkedInputStream in = new ChunkedInputStream(inbuffer, inputStream); final byte[] buffer = new byte[64]; diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStreamWithEofInputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStreamWithEofInputStream.java index ad5a570703..490feded4c 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStreamWithEofInputStream.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStreamWithEofInputStream.java @@ -49,8 +49,8 @@ public void setup() throws Exception { @Test public void testReadAtEof() throws Exception { Mockito.when(instream.read()).thenReturn(0); - Mockito.when(instream.atEof()).thenReturn(true); - Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); + Mockito.when(instream.atEof()).thenReturn(true); + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); Assert.assertEquals(0, eofstream.read()); Assert.assertNull(eofstream.getWrappedStream()); @@ -61,8 +61,8 @@ public void testReadAtEof() throws Exception { @Test public void testReadNotAtEof() throws Exception { Mockito.when(instream.read()).thenReturn(0); - Mockito.when(instream.atEof()).thenReturn(false); - Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); + Mockito.when(instream.atEof()).thenReturn(false); + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); Assert.assertEquals(0, eofstream.read()); Assert.assertNotNull(eofstream.getWrappedStream()); @@ -74,8 +74,8 @@ public void testReadNotAtEof() throws Exception { public void testReadByteArrayAtEof() throws Exception { Mockito.when(instream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt())) .thenReturn(1); - Mockito.when(instream.atEof()).thenReturn(true); - Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); + Mockito.when(instream.atEof()).thenReturn(true); + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); final byte[] tmp = new byte[1]; @@ -89,7 +89,7 @@ public void testReadByteArrayAtEof() throws Exception { public void testReadByteArrayNotAtEof() throws Exception { Mockito.when(instream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt())) .thenReturn(1); - Mockito.when(instream.atEof()).thenReturn(false); + Mockito.when(instream.atEof()).thenReturn(false); final byte[] tmp = new byte[1];