diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java index 31035134c..1c0cd7bb5 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java @@ -145,7 +145,7 @@ public final class HttpConnection { } while (true) { - String statusLineString = readLine(); + String statusLineString = source.readUtf8Line(true); StatusLine statusLine = new StatusLine(statusLineString); Response.Builder responseBuilder = new Response.Builder() @@ -166,28 +166,11 @@ public final class HttpConnection { /** Reads headers or trailers into {@code builder}. */ public void readHeaders(Headers.Builder builder) throws IOException { // parse the result headers until the first blank line - for (String line; (line = readLine()).length() != 0; ) { + for (String line; (line = source.readUtf8Line(true)).length() != 0; ) { builder.addLine(line); } } - private String readLine() throws IOException { - long newline = source.seek((byte) '\n'); - - if (newline > 0 && source.buffer().getByte(newline - 1) == '\r') { - // Read everything until '\r\n', then skip the '\r\n'. - String result = source.readUtf8((int) (newline - 1)); - source.skip(2); - return result; - - } else { - // Read everything until '\n', then skip the '\n'. - String result = source.readUtf8((int) (newline)); - source.skip(1); - return result; - } - } - /** * Discards the response body so that the connection can be reused and the * cache entry can be completed. This needs to be done judiciously, since it @@ -530,9 +513,9 @@ public final class HttpConnection { private void readChunkSize() throws IOException { // read the suffix of the previous chunk if (bytesRemainingInChunk != NO_CHUNK_YET) { - readLine(); + source.readUtf8Line(true); } - String chunkSizeString = readLine(); + String chunkSizeString = source.readUtf8Line(true); int index = chunkSizeString.indexOf(";"); if (index != -1) { chunkSizeString = chunkSizeString.substring(0, index); diff --git a/okio/src/main/java/okio/BufferedSource.java b/okio/src/main/java/okio/BufferedSource.java index cce5e3f4f..daacd4203 100644 --- a/okio/src/main/java/okio/BufferedSource.java +++ b/okio/src/main/java/okio/BufferedSource.java @@ -40,14 +40,19 @@ public interface BufferedSource extends Source { */ void require(long byteCount) throws IOException; + /** Removes a byte from the front of this buffer and returns it. */ byte readByte() throws IOException; + /** Removes a Big-Endian short from the front of this buffer and returns it. */ short readShort() throws IOException; + /** Removes a Little-Endian short from the front of this buffer and returns it. */ int readShortLe() throws IOException; + /** Removes a Big-Endian int from the front of this buffer and returns it. */ int readInt() throws IOException; + /** Removes a Little-Endian int from the front of this buffer and returns it. */ int readIntLe() throws IOException; /** @@ -57,10 +62,34 @@ public interface BufferedSource extends Source { */ void skip(long byteCount) throws IOException; + /** Removes {@code byteCount} bytes from this and returns them as a byte string. */ ByteString readByteString(int byteCount) throws IOException; + /** + * Removes {@code byteCount} bytes from this, decodes them as UTF-8 and + * returns the string. + */ String readUtf8(int byteCount) throws IOException; + /** + * Removes and returns characters up to but not including the next line break. + * A line break is either {@code "\n"} or {@code "\r\n"}; these characters are + * not included in the result. + * + *

This method supports two ways to handle the end of the stream: + *

+ */ + String readUtf8Line(boolean throwOnEof) throws IOException; + /** * Returns the index of {@code b} in the buffer, refilling it if necessary * until it is found. This reads an unbounded number of bytes into the buffer. diff --git a/okio/src/main/java/okio/OkBuffer.java b/okio/src/main/java/okio/OkBuffer.java index 10f3ea884..cc9d9abe2 100644 --- a/okio/src/main/java/okio/OkBuffer.java +++ b/okio/src/main/java/okio/OkBuffer.java @@ -139,7 +139,6 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable { return result; } - /** Removes a byte from the front of this buffer and returns it. */ @Override public byte readByte() { if (byteCount == 0) throw new IllegalStateException("byteCount == 0"); @@ -171,7 +170,6 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable { } } - /** Removes a Big-Endian short from the front of this buffer and returns it. */ @Override public short readShort() { if (byteCount < 2) throw new IllegalArgumentException("byteCount < 2: " + byteCount); @@ -201,7 +199,6 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable { return (short) s; } - /** Removes a Big-Endian int from the front of this buffer and returns it. */ @Override public int readInt() { if (byteCount < 4) throw new IllegalArgumentException("byteCount < 4: " + byteCount); @@ -234,22 +231,18 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable { return i; } - /** Removes a Little-Endian short from the front of this buffer and returns it. */ public int readShortLe() { return Util.reverseBytesShort(readShort()); } - /** Removes a Little-Endian int from the front of this buffer and returns it. */ public int readIntLe() { return Util.reverseBytesInt(readInt()); } - /** Removes {@code byteCount} bytes from this and returns them as a byte string. */ public ByteString readByteString(int byteCount) { return new ByteString(readBytes(byteCount)); } - /** Removes {@code byteCount} bytes from this, decodes them as UTF-8 and returns the string. */ public String readUtf8(int byteCount) { checkOffsetAndCount(this.byteCount, 0, byteCount); if (byteCount == 0) return ""; @@ -272,6 +265,28 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable { return result; } + @Override public String readUtf8Line(boolean throwOnEof) throws EOFException { + long newline = indexOf((byte) '\n'); + + if (newline == -1) { + if (throwOnEof) throw new EOFException(); + return byteCount != 0 ? readUtf8((int) byteCount) : null; + } + + if (newline > 0 && getByte(newline - 1) == '\r') { + // Read everything until '\r\n', then skip the '\r\n'. + String result = readUtf8((int) (newline - 1)); + skip(2); + return result; + + } else { + // Read everything until '\n', then skip the '\n'. + String result = readUtf8((int) (newline)); + skip(1); + return result; + } + } + private byte[] readBytes(int byteCount) { checkOffsetAndCount(this.byteCount, 0, byteCount); diff --git a/okio/src/main/java/okio/RealBufferedSource.java b/okio/src/main/java/okio/RealBufferedSource.java index f4f4ae62c..16200cec6 100644 --- a/okio/src/main/java/okio/RealBufferedSource.java +++ b/okio/src/main/java/okio/RealBufferedSource.java @@ -79,6 +79,31 @@ final class RealBufferedSource implements BufferedSource { return buffer.readUtf8(byteCount); } + @Override public String readUtf8Line(boolean throwOnEof) throws IOException { + long start = 0; + long newline; + while ((newline = buffer.indexOf((byte) '\n', start)) == -1) { + start = buffer.byteCount; + if (source.read(buffer, Segment.SIZE) == -1) { + if (throwOnEof) throw new EOFException(); + return buffer.byteCount != 0 ? readUtf8((int) buffer.byteCount) : null; + } + } + + if (newline > 0 && buffer.getByte(newline - 1) == '\r') { + // Read everything until '\r\n', then skip the '\r\n'. + String result = readUtf8((int) (newline - 1)); + skip(2); + return result; + + } else { + // Read everything until '\n', then skip the '\n'. + String result = readUtf8((int) (newline)); + skip(1); + return result; + } + } + @Override public short readShort() throws IOException { require(2); return buffer.readShort(); diff --git a/okio/src/test/java/okio/OkBufferReadUtf8LineTest.java b/okio/src/test/java/okio/OkBufferReadUtf8LineTest.java new file mode 100644 index 000000000..ac3de728e --- /dev/null +++ b/okio/src/test/java/okio/OkBufferReadUtf8LineTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * Licensed 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. + */ +package okio; + +public final class OkBufferReadUtf8LineTest extends ReadUtf8LineTest { + @Override protected BufferedSource newSource(String s) { + return new OkBuffer().writeUtf8(s); + } +} diff --git a/okio/src/test/java/okio/ReadUtf8LineTest.java b/okio/src/test/java/okio/ReadUtf8LineTest.java new file mode 100644 index 000000000..db36a7939 --- /dev/null +++ b/okio/src/test/java/okio/ReadUtf8LineTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * Licensed 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. + */ +package okio; + +import java.io.EOFException; +import java.io.IOException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public abstract class ReadUtf8LineTest { + protected abstract BufferedSource newSource(String s); + + @Test public void readLines() throws IOException { + BufferedSource source = newSource("abc\ndef\n"); + assertEquals("abc", source.readUtf8Line(true)); + assertEquals("def", source.readUtf8Line(true)); + try { + source.readUtf8Line(true); + fail(); + } catch (EOFException expected) { + } + } + + @Test public void emptyLines() throws IOException { + BufferedSource source = newSource("\n\n\n"); + assertEquals("", source.readUtf8Line(true)); + assertEquals("", source.readUtf8Line(true)); + assertEquals("", source.readUtf8Line(true)); + assertTrue(source.exhausted()); + } + + @Test public void crDroppedPrecedingLf() throws IOException { + BufferedSource source = newSource("abc\r\ndef\r\nghi\rjkl\r\n"); + assertEquals("abc", source.readUtf8Line(true)); + assertEquals("def", source.readUtf8Line(true)); + assertEquals("ghi\rjkl", source.readUtf8Line(true)); + } + + @Test public void bufferedReaderCompatible() throws IOException { + BufferedSource source = newSource("abc\ndef"); + assertEquals("abc", source.readUtf8Line(false)); + assertEquals("def", source.readUtf8Line(false)); + assertEquals(null, source.readUtf8Line(false)); + } + + @Test public void bufferedReaderCompatibleWithTrailingNewline() throws IOException { + BufferedSource source = newSource("abc\ndef\n"); + assertEquals("abc", source.readUtf8Line(false)); + assertEquals("def", source.readUtf8Line(false)); + assertEquals(null, source.readUtf8Line(false)); + } +} diff --git a/okio/src/test/java/okio/RealBufferedSourceReadUtf8LineTest.java b/okio/src/test/java/okio/RealBufferedSourceReadUtf8LineTest.java new file mode 100644 index 000000000..8793640b1 --- /dev/null +++ b/okio/src/test/java/okio/RealBufferedSourceReadUtf8LineTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * Licensed 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. + */ +package okio; + +import java.io.IOException; + +public final class RealBufferedSourceReadUtf8LineTest extends ReadUtf8LineTest { + /** Returns a buffered source that gets bytes of {@code data} one at a time. */ + @Override protected BufferedSource newSource(String s) { + final OkBuffer buffer = new OkBuffer().writeUtf8(s); + + Source slowSource = new Source() { + @Override public long read(OkBuffer sink, long byteCount) throws IOException { + return buffer.read(sink, Math.min(1, byteCount)); + } + + @Override public Source deadline(Deadline deadline) { + throw new UnsupportedOperationException(); + } + + @Override public void close() throws IOException { + throw new UnsupportedOperationException(); + } + }; + + return Okio.buffer(slowSource); + } +}