From 80257af81bbdbd015e81324a7b5c27b763b9d512 Mon Sep 17 00:00:00 2001 From: Andy Stadler Date: Tue, 19 May 2009 14:54:49 -0700 Subject: [PATCH] AI 149020: Manually merge CLs 148814, 148818 which fix IMAP response parsing to be able to handle a literal string in the middle of the response. BUG=1814528 Automated import of CL 149020 --- .../email/mail/store/ImapResponseParser.java | 31 +++++++ .../android/email/mail/store/ImapStore.java | 16 ++++ .../store/ImapResponseParserUnitTests.java | 86 +++++++++++++++++++ .../email/mail/store/ImapStoreUnitTests.java | 29 +++++++ 4 files changed, 162 insertions(+) create mode 100644 tests/src/com/android/email/mail/store/ImapResponseParserUnitTests.java diff --git a/src/com/android/email/mail/store/ImapResponseParser.java b/src/com/android/email/mail/store/ImapResponseParser.java index 410be6601..2118ab279 100644 --- a/src/com/android/email/mail/store/ImapResponseParser.java +++ b/src/com/android/email/mail/store/ImapResponseParser.java @@ -358,6 +358,37 @@ public class ImapResponseParser { boolean mCommandContinuationRequested; String mTag; + /* + * Return true if this response is completely read and parsed. + */ + public boolean completed() { + return mCompleted; + } + + /* + * Nail down the last element that possibly is FixedLengthInputStream literal. + */ + public void nailDown() throws IOException { + int last = size() - 1; + if (last >= 0) { + Object o = get(last); + if (o instanceof FixedLengthInputStream) { + FixedLengthInputStream is = (FixedLengthInputStream) o; + byte[] buffer = new byte[is.available()]; + is.read(buffer); + set(last, (Object) new String(buffer)); + } + } + } + + /* + * Append all response elements to this and copy completed flag. + */ + public void appendAll(ImapResponse other) { + addAll(other); + mCompleted = other.mCompleted; + } + public boolean more() throws IOException { if (mCompleted) { return false; diff --git a/src/com/android/email/mail/store/ImapStore.java b/src/com/android/email/mail/store/ImapStore.java index 7b234bbe0..b614ece8b 100644 --- a/src/com/android/email/mail/store/ImapStore.java +++ b/src/com/android/email/mail/store/ImapStore.java @@ -1216,9 +1216,25 @@ public class ImapStore extends Store { String tag = sendCommand(command, sensitive); ArrayList responses = new ArrayList(); ImapResponse response; + ImapResponse previous = null; do { + // This is work around to parse literal in the middle of response. + // We should nail down the previous response literal string if any. + if (previous != null && !previous.completed()) { + previous.nailDown(); + } response = mParser.readResponse(); + // This is work around to parse literal in the middle of response. + // If we found unmatched tagged response, it possibly be the continuous + // response just after the literal string. + if (response.mTag != null && !response.mTag.equals(tag) + && previous != null && !previous.completed()) { + previous.appendAll(response); + response.mTag = null; + continue; + } responses.add(response); + previous = response; } while (response.mTag == null); if (response.size() < 1 || !response.get(0).equals("OK")) { throw new ImapException(response.toString(), response.getAlertText()); diff --git a/tests/src/com/android/email/mail/store/ImapResponseParserUnitTests.java b/tests/src/com/android/email/mail/store/ImapResponseParserUnitTests.java new file mode 100644 index 000000000..011331454 --- /dev/null +++ b/tests/src/com/android/email/mail/store/ImapResponseParserUnitTests.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * 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 com.android.email.mail.store; + +import com.android.email.FixedLengthInputStream; +import com.android.email.mail.store.ImapResponseParser.ImapList; +import com.android.email.mail.store.ImapResponseParser.ImapResponse; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.ByteArrayInputStream; + +/** + * This is a series of unit tests for the ImapStore class. These tests must be locally + * complete - no server(s) required. + */ +@SmallTest +public class ImapResponseParserUnitTests extends AndroidTestCase { + + // TODO more comprehensive test for parsing + + /** + * Test for parsing literal string + */ + public void testParseLiteral() throws Exception { + ByteArrayInputStream is = new ByteArrayInputStream( + ("* STATUS \"INBOX\" (UNSEEN 2)\r\n" + + "100 OK STATUS completed\r\n" + + "* STATUS {5}\r\n" + + "INBOX (UNSEEN 10)\r\n" + + "101 OK STATUS completed\r\n") + .getBytes()); + ImapResponseParser parser = new ImapResponseParser(is); + + ImapResponse line1 = parser.readResponse(); + assertNull("Line 1 tag", line1.mTag); + assertTrue("Line 1 completed", line1.completed()); + assertEquals("Line 1 count", 3, line1.size()); + + ImapResponse line2 = parser.readResponse(); + assertEquals("Line 2 tag", "100", line2.mTag); + assertTrue("Line 2 completed", line2.completed()); + assertEquals("Line 2 count", 3, line2.size()); + + ImapResponse line3 = parser.readResponse(); + assertNull("Line 3 tag", line3.mTag); + assertFalse("Line 3 completed", line3.completed()); + assertEquals("Line 3 count", 2, line3.size()); + assertEquals("Line 3 word 2 class", FixedLengthInputStream.class, line3.get(1).getClass()); + + line3.nailDown(); + assertEquals("Line 3 word 2 nailed down", String.class, line3.get(1).getClass()); + assertEquals("Line 3 word 2 value", "INBOX", line3.getString(1)); + + ImapResponse line4 = parser.readResponse(); + assertEquals("Line 4 tag", "", line4.mTag); + assertTrue("Line 4 completed", line4.completed()); + assertEquals("Line 4 count", 1, line4.size()); + + line3.appendAll(line4); + assertNull("Line 3-4 tag", line3.mTag); + assertTrue("Line 3-4 completed", line3.completed()); + assertEquals("Line 3-4 count", 3, line3.size()); + assertEquals("Line 3-4 word 3 class", ImapList.class, line3.get(2).getClass()); + + ImapResponse line5 = parser.readResponse(); + assertEquals("Line 5 tag", "101", line5.mTag); + assertTrue("Line 5 completed", line5.completed()); + assertEquals("Line 5 count", 3, line5.size()); + } +} diff --git a/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java b/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java index 62a5e5297..550e6ac4d 100644 --- a/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java +++ b/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java @@ -176,4 +176,33 @@ public class ImapStoreUnitTests extends AndroidTestCase { "* OK [UIDNEXT 1]", "2 OK [READ-WRITE] INBOX selected. (Success)"}); } + + /** + * Test for getUnreadMessageCount with quoted string in the middle of response. + */ + public void testGetUnreadMessageCountWithQuotedString() throws Exception { + MockTransport mock = openAndInjectMockTransport(); + setupOpenFolder(mock); + mock.expect("3 STATUS \"INBOX\" \\(UNSEEN\\)", new String[] { + "* STATUS \"INBOX\" (UNSEEN 2)", + "3 OK STATUS completed"}); + mFolder.open(OpenMode.READ_WRITE, null); + int unreadCount = mFolder.getUnreadMessageCount(); + assertEquals("getUnreadMessageCount with quoted string", 2, unreadCount); + } + + /** + * Test for getUnreadMessageCount with literal string in the middle of response. + */ + public void testGetUnreadMessageCountWithLiteralString() throws Exception { + MockTransport mock = openAndInjectMockTransport(); + setupOpenFolder(mock); + mock.expect("3 STATUS \"INBOX\" \\(UNSEEN\\)", new String[] { + "* STATUS {5}", + "INBOX (UNSEEN 10)", + "3 OK STATUS completed"}); + mFolder.open(OpenMode.READ_WRITE, null); + int unreadCount = mFolder.getUnreadMessageCount(); + assertEquals("getUnreadMessageCount with literal string", 10, unreadCount); + } }