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:
+ *
+ * - Throw on EOF. Every call must consume either '\r\n'
+ * or '\n'. If these characters are absent in the stream, an {@link
+ * java.io.EOFException} is thrown. Use this for machine-generated data
+ * where a missing line break implies truncated input.
+ *
- Don't throw, just like BufferedReader. If the source
+ * doesn't end with a line break then an implicit line break is assumed.
+ * Null is returned once the source is exhausted. Use this for
+ * human-generated data, where a trailing line breaks are optional.
+ *
+ */
+ 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);
+ }
+}