diff --git a/src/main/java/org/cactoos/http/HtHead.java b/src/main/java/org/cactoos/http/HtHead.java index 039ea73..e01caf8 100644 --- a/src/main/java/org/cactoos/http/HtHead.java +++ b/src/main/java/org/cactoos/http/HtHead.java @@ -25,9 +25,9 @@ package org.cactoos.http; import java.io.InputStream; -import java.io.SequenceInputStream; +import java.nio.charset.Charset; +import java.util.Scanner; import org.cactoos.Input; -import org.cactoos.io.DeadInputStream; import org.cactoos.io.InputStreamOf; /** @@ -38,9 +38,14 @@ public final class HtHead implements Input { /** - * Buffer length. + * Header separator. */ - private static final int LENGTH = 16384; + private static final String DELIMITER = "\r\n\r\n"; + + /** + * Charset that is used to read headers. + */ + private static final Charset CHARSET = Charset.defaultCharset(); /** * Response. @@ -56,50 +61,14 @@ public HtHead(final Input rsp) { } @Override - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public InputStream stream() throws Exception { - final InputStream stream = this.response.stream(); - final byte[] buf = new byte[HtHead.LENGTH]; - InputStream head = new DeadInputStream(); - while (true) { - final int len = stream.read(buf); - if (len < 0) { - break; - } - final int tail = HtHead.findEnd(buf, len); - final byte[] temp = new byte[tail]; - System.arraycopy(buf, 0, temp, 0, tail); - head = new SequenceInputStream(head, new InputStreamOf(temp)); - if (tail != len) { - break; - } + try (final Scanner scanner = new Scanner( + this.response.stream(), + HtHead.CHARSET.name() + )) { + scanner.useDelimiter(HtHead.DELIMITER); + return new InputStreamOf(scanner.next()); } - return head; - } - - /** - * Find header end. - * @param buf Buffer where to search - * @param len Size of the buffer - * @return End of the header - */ - private static int findEnd(final byte[] buf, final int len) { - final byte[] end = {'\r', '\n', '\r', '\n'}; - int tail = end.length - 1; - while (tail < len) { - boolean found = true; - for (int num = 0; num < end.length; ++num) { - if (end[num] != buf[tail - end.length + 1 + num]) { - found = false; - break; - } - } - if (found) { - tail = tail - end.length + 1; - break; - } - ++tail; - } - return tail; } } + diff --git a/src/test/java/org/cactoos/http/HtHeadTest.java b/src/test/java/org/cactoos/http/HtHeadTest.java index b2d0b77..ecd7fa8 100644 --- a/src/test/java/org/cactoos/http/HtHeadTest.java +++ b/src/test/java/org/cactoos/http/HtHeadTest.java @@ -23,15 +23,20 @@ */ package org.cactoos.http; -import java.io.IOException; import java.util.Random; +import org.cactoos.Text; import org.cactoos.io.BytesOf; import org.cactoos.io.InputOf; import org.cactoos.text.JoinedText; +import org.cactoos.text.RepeatedText; +import org.cactoos.text.ReplacedText; import org.cactoos.text.TextOf; -import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.hamcrest.core.IsEqual; import org.junit.Test; +import org.llorllale.cactoos.matchers.Assertion; +import org.llorllale.cactoos.matchers.EndsWith; +import org.llorllale.cactoos.matchers.StartsWith; import org.llorllale.cactoos.matchers.TextHasString; /** @@ -45,9 +50,10 @@ public final class HtHeadTest { @Test - public void takesHeadOutOfHttpResponse() throws IOException { - MatcherAssert.assertThat( - new TextOf( + public void takesHeadOutOfHttpResponse() { + new Assertion<>( + "Header does not have 'text/plain'", + () -> new TextOf( new HtHead( new InputOf( new JoinedText( @@ -59,15 +65,16 @@ public void takesHeadOutOfHttpResponse() throws IOException { ) ) ) - ).asString(), - Matchers.endsWith("text/plain") - ); + ), + new EndsWith("text/plain") + ).affirm(); } @Test - public void emptyHeadOfHttpResponse() throws IOException { - MatcherAssert.assertThat( - new TextOf( + public void emptyHeadOfHttpResponse() { + new Assertion<>( + "Text does not have an empty string", + () -> new TextOf( new HtHead( new InputOf( new JoinedText( @@ -80,16 +87,17 @@ public void emptyHeadOfHttpResponse() throws IOException { ) ), new TextHasString("") - ); + ).affirm(); } @Test - public void largeText() throws IOException { + public void largeText() { //@checkstyle MagicNumberCheck (1 lines) final byte[] bytes = new byte[18000]; new Random().nextBytes(bytes); - MatcherAssert.assertThat( - new TextOf( + new Assertion<>( + "Header does not have text/plain header", + () -> new TextOf( new HtHead( new InputOf( new JoinedText( @@ -101,9 +109,55 @@ public void largeText() throws IOException { ) ) ) - ).asString(), - Matchers.endsWith("text/plain") - ); + ), + new EndsWith("text/plain") + ).affirm(); } + @Test + public void edgeOfTheBlockTearing() throws Exception { + final int size = 16384; + final Text header = new JoinedText( + "\r\n", + "HTTP/1.1 200 OK", + "Referer: http://en.wikipedia.org/wiki/Main_Page#\0", + "Content-type: text/plain", + "" + ); + final Text block = new ReplacedText( + header, + "\0", + new RepeatedText( + "x", + size - header.asString().length() + 1 + ).asString() + ); + new Assertion<>( + "make sure the constructed block is exact size", + () -> block.asString().length(), + new IsEqual<>( + size + ) + ).affirm(); + new Assertion<>( + String.format("Edge of the block tearing for size: %s", size), + () -> new TextOf( + new HtHead( + new InputOf( + new JoinedText( + "\r\n", + block.asString(), + "", + "body here" + ) + ) + ) + ), + Matchers.allOf( + new StartsWith("HTTP"), + new TextHasString("OK\r\nReferer"), + new EndsWith("text/plain") + ) + ).affirm(); + } }