Skip to content

Commit

Permalink
Merge branch 'ericedens-master'
Browse files Browse the repository at this point in the history
* ericedens-master:
  Allow length of readUtf8LineStrict to be limited.
  • Loading branch information
swankjesse committed Jan 16, 2017
2 parents c18de71 + d1d1c7b commit e8d6172
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 14 deletions.
29 changes: 23 additions & 6 deletions okio/src/main/java/okio/Buffer.java
Expand Up @@ -642,11 +642,16 @@ int selectPrefix(Options options) {
}

@Override public String readUtf8LineStrict() throws EOFException {
long newline = indexOf((byte) '\n');
return readUtf8LineStrict(Long.MAX_VALUE);
}

@Override public String readUtf8LineStrict(long limit) throws EOFException {
if (limit < 0) throw new IllegalArgumentException("limit < 0: " + limit);
long newline = indexOf((byte) '\n', 0, limit);
if (newline == -1) {
Buffer data = new Buffer();
copyTo(data, 0, Math.min(32, size));
throw new EOFException("\\n not found: size=" + size()
throw new EOFException("\\n not found: scanLength=" + Math.min(size(), limit)
+ " content=" + data.readByteString().hex() + "…");
}
return readUtf8Line(newline);
Expand Down Expand Up @@ -1263,15 +1268,25 @@ Segment writableSegment(int minimumCapacity) {
}

@Override public long indexOf(byte b) {
return indexOf(b, 0);
return indexOf(b, 0, Long.MAX_VALUE);
}

/**
* Returns the index of {@code b} in this at or beyond {@code fromIndex}, or
* -1 if this buffer does not contain {@code b} in that range.
*/
@Override public long indexOf(byte b, long fromIndex) {
if (fromIndex < 0) throw new IllegalArgumentException("fromIndex < 0");
return indexOf(b, fromIndex, Long.MAX_VALUE);
}

@Override public long indexOf(byte b, long fromIndex, long toIndex) {
if (fromIndex < 0 || toIndex < fromIndex) {
throw new IllegalArgumentException(
String.format("size=%s fromIndex=%s toIndex=%s", size, fromIndex, toIndex));
}

if (toIndex > size) toIndex = size;
if (fromIndex == toIndex) return -1L;

Segment s;
long offset;
Expand Down Expand Up @@ -1301,9 +1316,11 @@ Segment writableSegment(int minimumCapacity) {
}

// Scan through the segments, searching for b.
while (offset < size) {
while (offset < toIndex) {
byte[] data = s.data;
for (int pos = (int) (s.pos + fromIndex - offset), limit = s.limit; pos < limit; pos++) {
int limit = (int) Math.min(s.limit, s.pos + toIndex - offset);
int pos = (int) (s.pos + fromIndex - offset);
for (; pos < limit; pos++) {
if (data[pos] == b) {
return pos - s.pos + offset;
}
Expand Down
34 changes: 34 additions & 0 deletions okio/src/main/java/okio/BufferedSource.java
Expand Up @@ -391,6 +391,30 @@ public interface BufferedSource extends Source {
*/
String readUtf8LineStrict() throws IOException;

/**
* Like {@link #readUtf8LineStrict()}, but this allows the caller to specify a maximum number of
* bytes to scan. If {@code limit} bytes are scanned without finding a line break, then an {@link
* java.io.EOFException} is thrown. A common use case is protecting against input that doesn't
* include {@code "\n"} or {@code "\r\n"}.
*
* <p>This method is safe. No bytes are discarded if the match fails, and the caller is free
* to try another match: <pre>{@code
*
* Buffer buffer = new Buffer();
* buffer.writeUtf8("12345\r\n");
*
* // This will throw! A newline character (\n) must be read within the limit.
* buffer.readUtf8LineStrict(5);
*
* // No bytes have been consumed so the caller can retry.
* assertEquals("12345", buffer.readUtf8LineStrict(100));
* }</pre>
*
* <p>The returned string be up to {@code limit - 1} UTF-8 bytes. If {@code limit == 0} this will
* always throw an {@code EOFException} because no bytes will be scanned.
*/
String readUtf8LineStrict(long limit) throws IOException;

/**
* Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary.
*
Expand Down Expand Up @@ -433,6 +457,16 @@ public interface BufferedSource extends Source {
*/
long indexOf(byte b, long fromIndex) throws IOException;

/**
* Returns the index of {@code b} if it is found in the range of {@code fromIndex} inclusive
* to {@code toIndex} exclusive. If {@code b} isn't found, or if {@code fromIndex == toIndex},
* then -1 is returned.
*
* <p>The scan terminates at either {@code toIndex} or the end of the buffer, whichever comes
* first. The maximum number of bytes scanned is {@code toIndex-fromIndex}.
*/
long indexOf(byte b, long fromIndex, long toIndex) throws IOException;

/** Equivalent to {@link #indexOf(ByteString, long) indexOf(bytes, 0)}. */
long indexOf(ByteString bytes) throws IOException;

Expand Down
24 changes: 19 additions & 5 deletions okio/src/main/java/okio/RealBufferedSource.java
Expand Up @@ -207,11 +207,16 @@ final class RealBufferedSource implements BufferedSource {
}

@Override public String readUtf8LineStrict() throws IOException {
long newline = indexOf((byte) '\n');
return readUtf8LineStrict(Long.MAX_VALUE);
}

@Override public String readUtf8LineStrict(long limit) throws IOException {
if (limit < 0) throw new IllegalArgumentException("limit < 0: " + limit);
long newline = indexOf((byte) '\n', 0, limit);
if (newline == -1L) {
Buffer data = new Buffer();
buffer.copyTo(data, 0, Math.min(32, buffer.size()));
throw new EOFException("\\n not found: size=" + buffer.size()
throw new EOFException("\\n not found: scanLength=" + Math.min(buffer.size(), limit)
+ " content=" + data.readByteString().hex() + "…");
}
return buffer.readUtf8Line(newline);
Expand Down Expand Up @@ -311,14 +316,22 @@ final class RealBufferedSource implements BufferedSource {
}

@Override public long indexOf(byte b) throws IOException {
return indexOf(b, 0);
return indexOf(b, 0, Long.MAX_VALUE);
}

@Override public long indexOf(byte b, long fromIndex) throws IOException {
return indexOf(b, fromIndex, Long.MAX_VALUE);
}

@Override public long indexOf(byte b, long fromIndex, long toIndex) throws IOException {
if (closed) throw new IllegalStateException("closed");
if (fromIndex < 0 || toIndex < fromIndex) {
throw new IllegalArgumentException(
String.format("fromIndex=%s toIndex=%s", fromIndex, toIndex));
}

while (true) {
long result = buffer.indexOf(b, fromIndex);
while (fromIndex < toIndex) {
long result = buffer.indexOf(b, fromIndex, toIndex);
if (result != -1) return result;

long lastBufferSize = buffer.size;
Expand All @@ -327,6 +340,7 @@ final class RealBufferedSource implements BufferedSource {
// Keep searching, picking up from where we left off.
fromIndex = Math.max(fromIndex, lastBufferSize);
}
return -1L;
}

@Override public long indexOf(ByteString bytes) throws IOException {
Expand Down
67 changes: 66 additions & 1 deletion okio/src/test/java/okio/BufferedSourceTest.java
Expand Up @@ -468,12 +468,77 @@ public static List<Object[]> parameters() {
assertEquals(-1, source.indexOf((byte) 'e'));
}

@Test public void indexOfByteWithOffset() throws IOException {
@Test public void indexOfByteWithStartOffset() throws IOException {
sink.writeUtf8("a").writeUtf8(repeat('b', Segment.SIZE)).writeUtf8("c");
assertEquals(-1, source.indexOf((byte) 'a', 1));
assertEquals(15, source.indexOf((byte) 'b', 15));
}

@Test public void indexOfByteWithBothOffsets() throws IOException {
byte a = (byte) 'a';
byte c = (byte) 'c';

int size;
if (factory == Factory.ONE_BYTE_AT_A_TIME) {
// Larger segment sizes for ONE_BYTE_AT_A_TIME
// cause Travis to kill the test.
size = Segment.SIZE + 10;
} else {
size = Segment.SIZE * 5;
}

byte[] bytes = new byte[size];
Arrays.fill(bytes, a);

// These are tricky places where the buffer
// starts, ends, or segments come together.
int[] points = {
0, 1, 2,
Segment.SIZE - 1, Segment.SIZE, Segment.SIZE + 1,
size / 2 - 1, size / 2, size / 2 + 1,
size - Segment.SIZE - 1, size - Segment.SIZE, size - Segment.SIZE + 1,
size - 3, size - 2, size - 1
};

// In each iteration, we write c to the known point and then search for it using different
// windows. Some of the windows don't overlap with c's position, and therefore a match shouldn't
// be found.
for (int p : points) {
bytes[p] = c;
sink.write(bytes);

assertEquals( p, source.indexOf(c, 0, size ));
assertEquals( p, source.indexOf(c, 0, p + 1 ));
assertEquals( p, source.indexOf(c, p, size ));
assertEquals( p, source.indexOf(c, p, p + 1 ));
assertEquals( p, source.indexOf(c, p / 2, p * 2 + 1));
assertEquals(-1, source.indexOf(c, 0, p / 2 ));
assertEquals(-1, source.indexOf(c, 0, p ));
assertEquals(-1, source.indexOf(c, 0, 0 ));
assertEquals(-1, source.indexOf(c, p, p ));

// Reset.
source.readUtf8();
bytes[p] = a;
}
}

@Test public void indexOfByteInvalidBoundsThrows() throws IOException {
sink.writeUtf8("abc");

try {
source.indexOf((byte) 'a', -1);
fail("Expected failure: fromIndex < 0");
} catch (IllegalArgumentException expected) {
}

try {
source.indexOf((byte) 'a', 10, 0);
fail("Expected failure: fromIndex > toIndex");
} catch (IllegalArgumentException expected) {
}
}

@Test public void indexOfByteString() throws IOException {
assertEquals(-1, source.indexOf(ByteString.encodeUtf8("flop")));

Expand Down
32 changes: 30 additions & 2 deletions okio/src/test/java/okio/ReadUtf8LineTest.java
Expand Up @@ -90,7 +90,35 @@ public static List<Object[]> parameters() {
source.readUtf8LineStrict();
fail();
} catch (EOFException expected) {
assertEquals("\\n not found: size=0 content=…", expected.getMessage());
assertEquals("\\n not found: scanLength=0 content=…", expected.getMessage());
}
}

@Test public void readUtf8LineStrictWithLimits() throws IOException {
data.writeUtf8("abc\ndef\r\nghi\n");
assertEquals("abc", source.readUtf8LineStrict(10));
assertEquals("def", source.readUtf8LineStrict(10));

try {
source.readUtf8LineStrict(3);
fail("Expected failure: maxRead must include \\n");
} catch (EOFException expected) {
assertEquals("\\n not found: scanLength=3 content=6768690a…", expected.getMessage());
}

// No bytes should be consumed after a failed match.
assertEquals("ghi", source.readUtf8LineStrict(10));
}

@Test public void readUtf8LineStrictNonPositive() throws IOException {
try {
source.readUtf8LineStrict(0);
} catch (EOFException expected) {
}
try {
source.readUtf8LineStrict(-1);
fail("Expected failure: limit must be positive");
} catch (IllegalArgumentException expected) {
}
}

Expand All @@ -100,7 +128,7 @@ public static List<Object[]> parameters() {
source.readUtf8LineStrict();
fail();
} catch (EOFException expected) {
assertEquals("\\n not found: size=33 content=616161616161616162626262626262626363636363636363"
assertEquals("\\n not found: scanLength=33 content=616161616161616162626262626262626363636363636363"
+ "6464646464646464…", expected.getMessage());
}
}
Expand Down

0 comments on commit e8d6172

Please sign in to comment.