Merge pull request #1931 from k9mail/GH-1919_fix_ImapResponseParser_bug
Properly handle exceptions thrown by ImapResponseCallback
This commit is contained in:
commit
a56f12f1be
4 changed files with 128 additions and 29 deletions
|
@ -69,4 +69,10 @@ public class FixedLengthInputStream extends InputStream {
|
|||
public String toString() {
|
||||
return String.format(Locale.US, "FixedLengthInputStream(in=%s, length=%d)", mIn.toString(), mLength);
|
||||
}
|
||||
|
||||
public void skipRemaining() throws IOException {
|
||||
while (available() > 0) {
|
||||
skip(available());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ class ImapResponseParser {
|
|||
}
|
||||
|
||||
if (exception != null) {
|
||||
throw new RuntimeException("readResponse(): Exception in callback method", exception);
|
||||
throw new ImapResponseParserException("readResponse(): Exception in callback method", exception);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -354,26 +354,32 @@ class ImapResponseParser {
|
|||
if (response.getCallback() != null) {
|
||||
FixedLengthInputStream fixed = new FixedLengthInputStream(inputStream, size);
|
||||
|
||||
Exception callbackException = null;
|
||||
Object result = null;
|
||||
try {
|
||||
result = response.getCallback().foundLiteral(response, fixed);
|
||||
} catch (IOException e) {
|
||||
// Pass IOExceptions through
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
// Catch everything else and save it for later.
|
||||
exception = e;
|
||||
callbackException = e;
|
||||
}
|
||||
|
||||
// Check if only some of the literal data was read
|
||||
int available = fixed.available();
|
||||
if (available > 0 && available != size) {
|
||||
// If so, skip the rest
|
||||
while (fixed.available() > 0) {
|
||||
fixed.skip(fixed.available());
|
||||
boolean someDataWasRead = fixed.available() != size;
|
||||
if (someDataWasRead) {
|
||||
if (result == null && callbackException == null) {
|
||||
throw new AssertionError("Callback consumed some data but returned no result");
|
||||
}
|
||||
|
||||
fixed.skipRemaining();
|
||||
}
|
||||
|
||||
if (callbackException != null) {
|
||||
if (exception == null) {
|
||||
exception = callbackException;
|
||||
}
|
||||
return "EXCEPTION";
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package com.fsck.k9.mail.store.imap;
|
||||
|
||||
|
||||
public class ImapResponseParserException extends RuntimeException {
|
||||
public ImapResponseParserException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ import static org.junit.Assert.fail;
|
|||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, sdk = 21)
|
||||
public class ImapResponseParserTest {
|
||||
private PeekableInputStream peekableInputStream;
|
||||
|
||||
|
||||
@Test
|
||||
public void testSimpleOkResponse() throws IOException {
|
||||
|
@ -231,7 +233,7 @@ public class ImapResponseParserTest {
|
|||
@Test
|
||||
public void testParseLiteralWithConsumingCallbackReturningNull() throws Exception {
|
||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
||||
TestImapResponseCallback callback = new TestImapResponseCallback(4, "cheeseburger");
|
||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndReturn(4, "cheeseburger");
|
||||
|
||||
ImapResponse response = parser.readResponse(callback);
|
||||
|
||||
|
@ -242,37 +244,88 @@ public class ImapResponseParserTest {
|
|||
@Test
|
||||
public void testParseLiteralWithNonConsumingCallbackReturningNull() throws Exception {
|
||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
||||
TestImapResponseCallback callback = new TestImapResponseCallback(0, null);
|
||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndReturn(0, null);
|
||||
|
||||
ImapResponse response = parser.readResponse(callback);
|
||||
|
||||
assertEquals(1, response.size());
|
||||
assertEquals("test", response.getString(0));
|
||||
assertTrue(callback.foundLiteralCalled);
|
||||
assertAllInputConsumed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResponse_withPartlyConsumingCallbackReturningNull_shouldThrow() throws Exception {
|
||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndReturn(2, null);
|
||||
|
||||
try {
|
||||
parser.readResponse(callback);
|
||||
fail();
|
||||
} catch (AssertionError e) {
|
||||
assertEquals("Callback consumed some data but returned no result", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResponse_withPartlyConsumingCallbackThatThrows_shouldReadAllDataAndThrow() throws Exception {
|
||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndThrow(2);
|
||||
|
||||
try {
|
||||
parser.readResponse(callback);
|
||||
fail();
|
||||
} catch (ImapResponseParserException e) {
|
||||
assertEquals("readResponse(): Exception in callback method", e.getMessage());
|
||||
assertEquals(ImapResponseParserTestException.class, e.getCause().getClass());
|
||||
}
|
||||
|
||||
assertAllInputConsumed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResponse_withCallbackThatThrowsRepeatedly_shouldConsumeAllInputAndThrowFirstException()
|
||||
throws Exception {
|
||||
ImapResponseParser parser = createParser("* {3}\r\none {3}\r\ntwo\r\n");
|
||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndThrow(3);
|
||||
|
||||
try {
|
||||
parser.readResponse(callback);
|
||||
fail();
|
||||
} catch (ImapResponseParserException e) {
|
||||
assertEquals("readResponse(): Exception in callback method", e.getMessage());
|
||||
assertEquals(ImapResponseParserTestException.class, e.getCause().getClass());
|
||||
assertEquals(0, ((ImapResponseParserTestException) e.getCause()).instanceNumber);
|
||||
}
|
||||
|
||||
assertAllInputConsumed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseLiteralWithIncompleteConsumingCallbackReturningString() throws Exception {
|
||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
||||
TestImapResponseCallback callback = new TestImapResponseCallback(2, "ninja");
|
||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndReturn(2, "ninja");
|
||||
|
||||
ImapResponse response = parser.readResponse(callback);
|
||||
|
||||
assertEquals(1, response.size());
|
||||
assertEquals("ninja", response.getString(0));
|
||||
assertAllInputConsumed();
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
@Test
|
||||
public void testParseLiteralWithThrowingCallback() throws Exception {
|
||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
||||
ImapResponseCallback callback = new ImapResponseCallback() {
|
||||
@Override
|
||||
public Object foundLiteral(ImapResponse response, FixedLengthInputStream literal) throws Exception {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
};
|
||||
ImapResponseCallback callback = TestImapResponseCallback.readBytesAndThrow(0);
|
||||
|
||||
parser.readResponse(callback);
|
||||
try {
|
||||
parser.readResponse(callback);
|
||||
fail();
|
||||
} catch (ImapResponseParserException e) {
|
||||
assertEquals("readResponse(): Exception in callback method", e.getMessage());
|
||||
}
|
||||
|
||||
assertAllInputConsumed();
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
|
@ -415,19 +468,34 @@ public class ImapResponseParserTest {
|
|||
|
||||
private ImapResponseParser createParser(String response) {
|
||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(response.getBytes());
|
||||
PeekableInputStream peekableInputStream = new PeekableInputStream(byteArrayInputStream);
|
||||
peekableInputStream = new PeekableInputStream(byteArrayInputStream);
|
||||
return new ImapResponseParser(peekableInputStream);
|
||||
}
|
||||
|
||||
private void assertAllInputConsumed() throws IOException {
|
||||
assertEquals(0, peekableInputStream.available());
|
||||
}
|
||||
|
||||
private class TestImapResponseCallback implements ImapResponseCallback {
|
||||
|
||||
static class TestImapResponseCallback implements ImapResponseCallback {
|
||||
private final int readNumberOfBytes;
|
||||
private final Object returnValue;
|
||||
private final boolean throwException;
|
||||
private int exceptionCount = 0;
|
||||
public boolean foundLiteralCalled = false;
|
||||
|
||||
TestImapResponseCallback(int readNumberOfBytes, Object returnValue) {
|
||||
public static TestImapResponseCallback readBytesAndReturn(int readNumberOfBytes, Object returnValue) {
|
||||
return new TestImapResponseCallback(readNumberOfBytes, returnValue, false);
|
||||
}
|
||||
|
||||
public static TestImapResponseCallback readBytesAndThrow(int readNumberOfBytes) {
|
||||
return new TestImapResponseCallback(readNumberOfBytes, null, true);
|
||||
}
|
||||
|
||||
private TestImapResponseCallback(int readNumberOfBytes, Object returnValue, boolean throwException) {
|
||||
this.readNumberOfBytes = readNumberOfBytes;
|
||||
this.returnValue = returnValue;
|
||||
this.throwException = throwException;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -435,17 +503,28 @@ public class ImapResponseParserTest {
|
|||
foundLiteralCalled = true;
|
||||
|
||||
int skipBytes = readNumberOfBytes;
|
||||
long skippedBytes;
|
||||
do {
|
||||
skippedBytes = literal.skip(skipBytes);
|
||||
while (skipBytes > 0) {
|
||||
long skippedBytes = literal.skip(skipBytes);
|
||||
skipBytes -= skippedBytes;
|
||||
} while (skippedBytes > 0);
|
||||
}
|
||||
|
||||
if (throwException) {
|
||||
throw new ImapResponseParserTestException(exceptionCount++);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestUntaggedHandler implements UntaggedHandler {
|
||||
static class ImapResponseParserTestException extends RuntimeException {
|
||||
public final int instanceNumber;
|
||||
|
||||
public ImapResponseParserTestException(int instanceNumber) {
|
||||
this.instanceNumber = instanceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
static class TestUntaggedHandler implements UntaggedHandler {
|
||||
public final List<ImapResponse> responses = new ArrayList<ImapResponse>();
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in a new issue