replicant-packages_apps_Email/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java

790 lines
33 KiB
Java

/*
* Copyright (C) 2008 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.Email;
import com.android.email.Utility;
import com.android.email.mail.Address;
import com.android.email.mail.Body;
import com.android.email.mail.FetchProfile;
import com.android.email.mail.Flag;
import com.android.email.mail.Folder;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Part;
import com.android.email.mail.Transport;
import com.android.email.mail.Folder.FolderType;
import com.android.email.mail.Folder.OpenMode;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeMultipart;
import com.android.email.mail.internet.MimeUtility;
import com.android.email.mail.internet.TextBody;
import com.android.email.mail.store.ImapStore.ImapMessage;
import com.android.email.mail.transport.MockTransport;
import org.apache.commons.io.IOUtils;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
import java.util.ArrayList;
import java.util.HashMap;
/**
* This is a series of unit tests for the ImapStore class. These tests must be locally
* complete - no server(s) required.
*
* To run these tests alone, use:
* $ runtest -c com.android.email.mail.store.ImapStoreUnitTests email
*/
@SmallTest
public class ImapStoreUnitTests extends AndroidTestCase {
private final static String[] NO_REPLY = new String[0];
/* These values are provided by setUp() */
private ImapStore mStore = null;
private ImapStore.ImapFolder mFolder = null;
private int mNextTag;
/**
* Setup code. We generate a lightweight ImapStore and ImapStore.ImapFolder.
*/
@Override
protected void setUp() throws Exception {
super.setUp();
Email.setTempDirectory(getContext());
// These are needed so we can get at the inner classes
mStore = (ImapStore) ImapStore.newInstance("imap://user:password@server:999",
getContext(), null);
mFolder = (ImapStore.ImapFolder) mStore.getFolder("INBOX");
}
/**
* Confirms simple non-SSL non-TLS login
*/
public void testSimpleLogin() throws MessagingException {
MockTransport mockTransport = openAndInjectMockTransport();
// try to open it
setupOpenFolder(mockTransport);
mFolder.open(OpenMode.READ_WRITE, null);
// TODO: inject specific facts in the initial folder SELECT and check them here
}
/**
* TODO: Test with SSL negotiation (faked)
* TODO: Test with SSL required but not supported
* TODO: Test with TLS negotiation (faked)
* TODO: Test with TLS required but not supported
* TODO: Test calling getMessageCount(), getMessages(), etc.
*/
/**
* Test the generation of the IMAP ID keys
*/
public void testImapIdBasic() {
// First test looks at operation of the outer API - we don't control any of the
// values; Just look for basic results.
// Strings we'll expect to find:
// name Android package name of the program
// os "android"
// os-version "version; build-id"
// vendor Vendor of the client/server
// x-android-device-model Model (Optional, so not tested here)
// x-android-net-operator Carrier (Unreliable, so not tested here)
// AGUID A device+account UID
String id = mStore.getImapId(getContext(), "user-name", "host-name", "IMAP4rev1 STARTTLS");
HashMap<String, String> map = tokenizeImapId(id);
assertEquals(getContext().getPackageName(), map.get("name"));
assertEquals("android", map.get("os"));
assertNotNull(map.get("os-version"));
assertNotNull(map.get("vendor"));
assertNotNull(map.get("AGUID"));
// Next, use the inner API to confirm operation of a couple of
// variants for release and non-release devices.
// simple API check - non-REL codename, non-empty version
id = mStore.makeCommonImapId("packageName", "version", "codeName",
"model", "id", "vendor", "network-operator");
map = tokenizeImapId(id);
assertEquals("packageName", map.get("name"));
assertEquals("android", map.get("os"));
assertEquals("version; id", map.get("os-version"));
assertEquals("vendor", map.get("vendor"));
assertEquals(null, map.get("x-android-device-model"));
assertEquals("network-operator", map.get("x-android-mobile-net-operator"));
assertEquals(null, map.get("AGUID"));
// simple API check - codename is REL, so use model name.
// also test empty version => 1.0 and empty network operator
id = mStore.makeCommonImapId("packageName", "", "REL",
"model", "id", "vendor", "");
map = tokenizeImapId(id);
assertEquals("packageName", map.get("name"));
assertEquals("android", map.get("os"));
assertEquals("1.0; id", map.get("os-version"));
assertEquals("vendor", map.get("vendor"));
assertEquals("model", map.get("x-android-device-model"));
assertEquals(null, map.get("x-android-mobile-net-operator"));
assertEquals(null, map.get("AGUID"));
}
/**
* Test of the internal generator for IMAP ID strings, specifically looking for proper
* filtering of illegal values. This is required because we cannot necessarily trust
* the external sources of some of this data (e.g. release labels).
*
* The (somewhat arbitrary) legal values are: a-z A-Z 0-9 - _ + = ; : . , / <space>
* The most important goal of the filters is to keep out control chars, (, ), and "
*/
public void testImapIdFiltering() {
String id = mStore.makeCommonImapId("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789", "codeName",
"model", "-_+=;:.,// ",
"v(e)n\"d\ro\nr", // look for bad chars stripped out, leaving OK chars
"()\""); // look for bad chars stripped out, leaving nothing
HashMap<String, String> map = tokenizeImapId(id);
assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", map.get("name"));
assertEquals("0123456789; -_+=;:.,// ", map.get("os-version"));
assertEquals("vendor", map.get("vendor"));
assertNull(map.get("x-android-mobile-net-operator"));
}
/**
* Test that IMAP ID uid's are per-username
*/
public void testImapIdDeviceId() throws MessagingException {
ImapStore store1a = (ImapStore) ImapStore.newInstance("imap://user1:password@server:999",
getContext(), null);
ImapStore store1b = (ImapStore) ImapStore.newInstance("imap://user1:password@server:999",
getContext(), null);
ImapStore store2 = (ImapStore) ImapStore.newInstance("imap://user2:password@server:999",
getContext(), null);
String id1a = mStore.getImapId(getContext(), "user1", "host-name", "IMAP4rev1");
String id1b = mStore.getImapId(getContext(), "user1", "host-name", "IMAP4rev1");
String id2 = mStore.getImapId(getContext(), "user2", "host-name", "IMAP4rev1");
String uid1a = tokenizeImapId(id1a).get("AGUID");
String uid1b = tokenizeImapId(id1b).get("AGUID");
String uid2 = tokenizeImapId(id2).get("AGUID");
assertEquals(uid1a, uid1b);
MoreAsserts.assertNotEqual(uid1a, uid2);
}
/**
* Helper to break an IMAP ID string into keys & values
* @param id the IMAP Id string (the part inside the parens)
* @return a map of key/value pairs
*/
private HashMap<String, String> tokenizeImapId(String id) {
// Instead of a true tokenizer, we'll use double-quote as the split.
// We can's use " " because there may be spaces inside the values.
String[] elements = id.split("\"");
HashMap<String, String> map = new HashMap<String, String>();
for (int i = 0; i < elements.length; ) {
// Because we split at quotes, we expect to find:
// [i] = null or one or more spaces
// [i+1] = key
// [i+2] = one or more spaces
// [i+3] = value
map.put(elements[i+1], elements[i+3]);
i += 4;
}
return map;
}
/**
* Test non-NIL server response to IMAP ID. We should simply ignore it.
*/
public void testServerId() throws MessagingException {
MockTransport mockTransport = openAndInjectMockTransport();
// try to open it
setupOpenFolder(mockTransport, new String[] {
"* ID (\"name\" \"Cyrus\" \"version\" \"1.5\"" +
" \"os\" \"sunos\" \"os-version\" \"5.5\"" +
" \"support-url\" \"mailto:cyrus-bugs+@andrew.cmu.edu\")",
"OK"}, "READ-WRITE");
mFolder.open(OpenMode.READ_WRITE, null);
}
/**
* Test OK response to IMAP ID with crummy text afterwards too.
*/
public void testImapIdOkParsing() throws MessagingException {
MockTransport mockTransport = openAndInjectMockTransport();
// try to open it
setupOpenFolder(mockTransport, new String[] {
"* ID NIL",
"OK [ID] bad-char-%"}, "READ-WRITE");
mFolder.open(OpenMode.READ_WRITE, null);
}
/**
* Test BAD response to IMAP ID - also with bad parser chars
*/
public void testImapIdBad() throws MessagingException {
MockTransport mockTransport = openAndInjectMockTransport();
// try to open it
setupOpenFolder(mockTransport, new String[] {
"BAD unknown command bad-char-%"}, "READ-WRITE");
mFolder.open(OpenMode.READ_WRITE, null);
}
/**
* TODO: Test the operation of checkSettings()
* TODO: Test small Store & Folder functions that manage folders & namespace
*/
/**
* Test small Folder functions that don't really do anything in Imap
* TODO: Test all of the small Folder functions.
*/
public void testSmallFolderFunctions() throws MessagingException {
// getPermanentFlags() returns { Flag.DELETED, Flag.SEEN, Flag.FLAGGED }
Flag[] flags = mFolder.getPermanentFlags();
assertEquals(3, flags.length);
// TODO: Write flags into hashset and compare them to a hashset and compare them
assertEquals(Flag.DELETED, flags[0]);
assertEquals(Flag.SEEN, flags[1]);
assertEquals(Flag.FLAGGED, flags[2]);
// canCreate() returns true
assertTrue(mFolder.canCreate(FolderType.HOLDS_FOLDERS));
assertTrue(mFolder.canCreate(FolderType.HOLDS_MESSAGES));
}
/**
* Lightweight test to confirm that IMAP hasn't implemented any folder roles yet.
*
* TODO: Test this with multiple folders provided by mock server
* TODO: Implement XLIST and then support this
*/
public void testNoFolderRolesYet() {
assertEquals(Folder.FolderRole.UNKNOWN, mFolder.getRole());
}
/**
* Lightweight test to confirm that IMAP isn't requesting structure prefetch.
*/
public void testNoStructurePrefetch() {
assertFalse(mStore.requireStructurePrefetch());
}
/**
* Lightweight test to confirm that IMAP is requesting sent-message-upload.
* TODO: Implement Gmail-specific cases and handle this server-side
*/
public void testSentUploadRequested() {
assertTrue(mStore.requireCopyMessageToSentFolder());
}
/**
* TODO: Test the process of opening and indexing a mailbox with one unread message in it.
*/
/**
* TODO: Test the scenario where the transport is "open" but not really (e.g. server closed).
/**
* Set up a basic MockTransport. open it, and inject it into mStore
*/
private MockTransport openAndInjectMockTransport() {
// Create mock transport and inject it into the ImapStore that's already set up
MockTransport mockTransport = new MockTransport();
mockTransport.setSecurity(Transport.CONNECTION_SECURITY_NONE, false);
mockTransport.setMockHost("mock.server.com");
mStore.setTransport(mockTransport);
return mockTransport;
}
/**
* Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
*
* @param mockTransport the mock transport we're using
*/
private void setupOpenFolder(MockTransport mockTransport) {
setupOpenFolder(mockTransport, "READ-WRITE");
}
/**
* Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
*
* @param mockTransport the mock transport we're using
*/
private void setupOpenFolder(MockTransport mockTransport, String readWriteMode) {
setupOpenFolder(mockTransport, new String[] {
"* ID NIL", "OK"}, readWriteMode);
}
/**
* Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
* Also allows setting a custom IMAP ID.
*
* Also sets mNextTag, an int, which is useful if there are additional commands to inject.
*
* @param mockTransport the mock transport we're using
* @param imapIdResponse the expected series of responses to the IMAP ID command. Non-final
* lines should be tagged with *. The final response should be untagged (the correct
* tag will be added at runtime).
* @param "READ-WRITE" or "READ-ONLY"
* @return the next tag# to use
*/
private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse,
String readWriteMode) {
// Fix the tag # of the ID response
String last = imapIdResponse[imapIdResponse.length-1];
last = "2 " + last;
imapIdResponse[imapIdResponse.length-1] = last;
// inject boilerplate commands that match our typical login
mockTransport.expect(null, "* OK Imap 2000 Ready To Assist You");
mockTransport.expect("1 CAPABILITY", new String[] {
"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED",
"1 OK CAPABILITY completed"});
mockTransport.expect("2 ID \\(.*\\)", imapIdResponse);
mockTransport.expect("3 LOGIN user \"password\"",
"3 OK user authenticated (Success)");
mockTransport.expect("4 SELECT \"INBOX\"", new String[] {
"* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
"* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
"* 0 EXISTS",
"* 0 RECENT",
"* OK [UNSEEN 0]",
"* OK [UIDNEXT 1]",
"4 OK [" + readWriteMode + "] INBOX selected. (Success)"});
mNextTag = 5;
}
/**
* Return a tag for use in setting up expect strings. Typically this is called in pairs,
* first as getNextTag(false) when emitting the command, then as getNextTag(true) when
* emitting the final line of the expected response.
* @param advance true to increment mNextTag for the subsequence command
* @return a string containing the current tag
*/
public String getNextTag(boolean advance) {
if (advance) ++mNextTag;
return Integer.toString(mNextTag);
}
/**
* Test that servers reporting READ-WRITE mode are parsed properly
* Note: the READ_WRITE mode passed to folder.open() does not affect the test
*/
public void testReadWrite() throws MessagingException {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock, "READ-WRITE");
mFolder.open(OpenMode.READ_WRITE, null);
assertEquals(OpenMode.READ_WRITE, mFolder.getMode());
}
/**
* Test that servers reporting READ-ONLY mode are parsed properly
* Note: the READ_ONLY mode passed to folder.open() does not affect the test
*/
public void testReadOnly() throws MessagingException {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock, "READ-ONLY");
mFolder.open(OpenMode.READ_ONLY, null);
assertEquals(OpenMode.READ_ONLY, mFolder.getMode());
}
/**
* Test for getUnreadMessageCount with quoted string in the middle of response.
*/
public void testGetUnreadMessageCountWithQuotedString() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mock.expect(getNextTag(false) + " STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
"* STATUS \"INBOX\" (UNSEEN 2)",
getNextTag(true) + " 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(getNextTag(false) + " STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
"* STATUS {5}",
"INBOX (UNSEEN 10)",
getNextTag(true) + " OK STATUS completed"});
mFolder.open(OpenMode.READ_WRITE, null);
int unreadCount = mFolder.getUnreadMessageCount();
assertEquals("getUnreadMessageCount with literal string", 10, unreadCount);
}
public void testFetchFlagEnvelope() throws MessagingException {
final MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.ENVELOPE);
mock.expect(getNextTag(false) +
" UID FETCH 1 \\(UID FLAGS INTERNALDATE RFC822\\.SIZE BODY\\.PEEK\\[HEADER.FIELDS" +
" \\(date subject from content-type to cc message-id\\)\\]\\)",
new String[] {
"* 9 FETCH (UID 1 RFC822.SIZE 120626 INTERNALDATE \"17-May-2010 22:00:15 +0000\"" +
"FLAGS (\\Seen) BODY[HEADER.FIELDS (date subject from content-type to cc" +
" message-id)]" +
" {279}",
"From: Xxxxxx Yyyyy <userxx@android.com>",
"Date: Mon, 17 May 2010 14:59:52 -0700",
"Message-ID: <x0000000000000000000000000000000000000000000000y@android.com>",
"Subject: ssubject",
"To: android.test01@android.com",
"Content-Type: multipart/mixed; boundary=a00000000000000000000000000b",
"",
")",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
assertEquals("android.test01@android.com", message.getHeader("to")[0]);
assertEquals("Xxxxxx Yyyyy <userxx@android.com>", message.getHeader("from")[0]);
assertEquals("multipart/mixed; boundary=a00000000000000000000000000b",
message.getHeader("Content-Type")[0]);
assertTrue(message.isSet(Flag.SEEN));
}
public void testFetchBodyStructure() throws MessagingException {
final MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.STRUCTURE);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
new String[] {
"* 9 FETCH (UID 1 BODYSTRUCTURE ((\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\")" +
" CID NIL \"7BIT\" 18 3 NIL NIL NIL)" +
"(\"IMAGE\" \"PNG\"" +
" (\"NAME\" \"device.png\") NIL NIL \"BASE64\" 117840 NIL (\"ATTACHMENT\"" +
"(\"FILENAME\" \"device.png\")) NIL)" +
"(\"TEXT\" \"HTML\"" +
" () NIL NIL \"7BIT\" 100 NIL NIL (\"ATTACHMENT\"" +
"(\"FILENAME\" \"attachment.html\" \"SIZE\" 555)) NIL)" +
"\"MIXED\" (\"BOUNDARY\" \"00032556278a7005e40486d159ca\") NIL NIL))",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
// Check mime structure...
Body body = message.getBody();
assertTrue(body instanceof MimeMultipart);
MimeMultipart mimeMultipart = (MimeMultipart) body;
assertEquals(3, mimeMultipart.getCount());
assertEquals("mixed", mimeMultipart.getSubTypeForTest());
Part part0 = mimeMultipart.getBodyPart(0);
Part part1 = mimeMultipart.getBodyPart(1);
Part part2 = mimeMultipart.getBodyPart(2);
assertTrue(part0 instanceof MimeBodyPart);
assertTrue(part1 instanceof MimeBodyPart);
assertTrue(part2 instanceof MimeBodyPart);
MimeBodyPart mimePart0 = (MimeBodyPart) part0; // text/plain
MimeBodyPart mimePart1 = (MimeBodyPart) part1; // image/png
MimeBodyPart mimePart2 = (MimeBodyPart) part2; // text/html
MoreAsserts.assertEquals(
new String[] {"text/plain;\n CHARSET=\"ISO-8859-1\""},
part0.getHeader("Content-Type")
);
MoreAsserts.assertEquals(
new String[] {"image/png;\n NAME=\"device.png\""},
part1.getHeader("Content-Type")
);
MoreAsserts.assertEquals(
new String[] {"text/html"},
part2.getHeader("Content-Type")
);
MoreAsserts.assertEquals(
new String[] {"CID"},
part0.getHeader("Content-ID")
);
assertNull(
part1.getHeader("Content-ID")
);
assertNull(
part2.getHeader("Content-ID")
);
MoreAsserts.assertEquals(
new String[] {"7BIT"},
part0.getHeader("Content-Transfer-Encoding")
);
MoreAsserts.assertEquals(
new String[] {"BASE64"},
part1.getHeader("Content-Transfer-Encoding")
);
MoreAsserts.assertEquals(
new String[] {"7BIT"},
part2.getHeader("Content-Transfer-Encoding")
);
MoreAsserts.assertEquals(
new String[] {";\n size=18"}, // TODO Is that right?
part0.getHeader("Content-Disposition")
);
MoreAsserts.assertEquals(
new String[] {"attachment;\n filename=\"device.png\";\n size=117840"},
part1.getHeader("Content-Disposition")
);
MoreAsserts.assertEquals(
new String[] {"attachment;\n filename=\"attachment.html\";\n size=\"555\""},
part2.getHeader("Content-Disposition")
);
// TODO Test for quote: If a filename contains ", it should become %22,
// which isn't implemented.
}
public void testFetchBodySane() throws MessagingException {
final MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.BODY_SANE);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]<0.51200>\\)",
new String[] {
"* 9 FETCH (UID 1 BODY[] {23}",
"from: a@b.com", // 15 bytes
"", // 2
"test", // 6
")",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
assertEquals("a@b.com", message.getHeader("from")[0]);
}
public void testFetchBody() throws MessagingException {
final MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.BODY);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]\\)",
new String[] {
"* 9 FETCH (UID 1 BODY[] {23}",
"from: a@b.com", // 15 bytes
"", // 2
"test", // 6
")",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
assertEquals("a@b.com", message.getHeader("from")[0]);
}
public void testFetchAttachment() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.STRUCTURE);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
new String[] {
"* 9 FETCH (UID 1 BODYSTRUCTURE ((\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\")" +
" CID NIL \"7BIT\" 18 3 NIL NIL NIL)" +
"(\"IMAGE\" \"PNG\"" +
" (\"NAME\" \"device.png\") NIL NIL \"BASE64\" 117840 NIL (\"ATTACHMENT\"" +
"(\"FILENAME\" \"device.png\")) NIL)" +
"\"MIXED\"))",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
// Check mime structure, and get the second part.
Body body = message.getBody();
assertTrue(body instanceof MimeMultipart);
MimeMultipart mimeMultipart = (MimeMultipart) body;
assertEquals(2, mimeMultipart.getCount());
Part part1 = mimeMultipart.getBodyPart(1);
assertTrue(part1 instanceof MimeBodyPart);
MimeBodyPart mimePart1 = (MimeBodyPart) part1;
// Fetch the second part
fp.clear();
fp.add(mimePart1);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[2\\]\\)",
new String[] {
"* 9 FETCH (UID 1 BODY[2] {4}",
"YWJj)", // abc in base64
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
assertEquals("abc",
Utility.fromUtf8(IOUtils.toByteArray(mimePart1.getBody().getInputStream())));
}
/**
* Test for proper operations on servers that return "NIL" for empty message bodies.
*/
public void testNilMessage() throws MessagingException {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
// Prepare to pull structure and peek body text - this is like the "large message"
// loop in MessagingController.synchronizeMailboxGeneric()
FetchProfile fp = new FetchProfile();fp.clear();
fp.add(FetchProfile.Item.STRUCTURE);
Message message1 = mFolder.createMessage("1");
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", new String[] {
"* 1 FETCH (UID 1 BODYSTRUCTURE (TEXT PLAIN NIL NIL NIL 7BIT 0 0 NIL NIL NIL))",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message1 }, fp, null);
// The expected result for an empty body is:
// * 1 FETCH (UID 1 BODY[TEXT] {0})
// But some servers are returning NIL for the empty body:
// * 1 FETCH (UID 1 BODY[TEXT] NIL)
// Because this breaks our little parser, fetch() skips over empty parts.
// The rest of this test is confirming that this is the case.
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[TEXT\\]\\)", new String[] {
"* 1 FETCH (UID 1 BODY[TEXT] NIL)",
getNextTag(true) + " OK SUCCESS"
});
ArrayList<Part> viewables = new ArrayList<Part>();
ArrayList<Part> attachments = new ArrayList<Part>();
MimeUtility.collectParts(message1, viewables, attachments);
assertTrue(viewables.size() == 1);
Part emptyBodyPart = viewables.get(0);
fp.clear();
fp.add(emptyBodyPart);
mFolder.fetch(new Message[] { message1 }, fp, null);
// If this wasn't working properly, there would be an attempted interpretation
// of the empty part's NIL and possibly a crash.
// If this worked properly, the "empty" body can now be retrieved
viewables = new ArrayList<Part>();
attachments = new ArrayList<Part>();
MimeUtility.collectParts(message1, viewables, attachments);
assertTrue(viewables.size() == 1);
emptyBodyPart = viewables.get(0);
String text = MimeUtility.getTextFromPart(emptyBodyPart);
assertNull(text);
}
/**
* Confirm the IMAP parser won't crash when seeing an excess FETCH response line without UID.
*
* <p>We've observed that the secure.emailsrvr.com email server returns an excess FETCH response
* for a UID FETCH command. These excess responses doesn't have the UID field in it, even
* though we request, which led the response parser to crash. We fixed it by ignoring response
* lines that don't have UID. This test is to make sure this case.
*/
public void testExcessFetchResult() throws MessagingException {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
// Create a message, and make sure it's not "SEEN".
Message message1 = mFolder.createMessage("1");
assertFalse(message1.isSet(Flag.SEEN));
FetchProfile fp = new FetchProfile();
fp.clear();
fp.add(FetchProfile.Item.FLAGS);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID FLAGS\\)",
new String[] {
"* 1 FETCH (UID 1 FLAGS (\\Seen))",
"* 2 FETCH (FLAGS (\\Seen))",
getNextTag(true) + " OK SUCCESS"
});
// Shouldn't crash
mFolder.fetch(new Message[] { message1 }, fp, null);
// And the message is "SEEN".
assertTrue(message1.isSet(Flag.SEEN));
}
public void testAppendMessages() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
ImapMessage message = (ImapMessage) mFolder.createMessage("1");
message.setFrom(new Address("me@test.com"));
message.setRecipient(RecipientType.TO, new Address("you@test.com"));
message.setMessageId("<message.id@test.com>");
message.setFlagDirectlyForTest(Flag.SEEN, true);
message.setBody(new TextBody("Test Body"));
// + go ahead
// * 12345 EXISTS
// OK [APPENDUID 627684530 17] (Success)
mock.expect(getNextTag(false) + " APPEND \\\"INBOX\\\" \\(\\\\Seen\\) \\{166\\}",
new String[] {"+ go ahead"});
mock.expectLiterally("From: me@test.com", NO_REPLY);
mock.expectLiterally("To: you@test.com", NO_REPLY);
mock.expectLiterally("Message-ID: <message.id@test.com>", NO_REPLY);
mock.expectLiterally("Content-Type: text/plain;", NO_REPLY);
mock.expectLiterally(" charset=utf-8", NO_REPLY);
mock.expectLiterally("Content-Transfer-Encoding: base64", NO_REPLY);
mock.expectLiterally("", NO_REPLY);
mock.expectLiterally("VGVzdCBCb2R5", NO_REPLY);
mock.expectLiterally("", new String[] {
"* 7 EXISTS",
getNextTag(true) + " OK [APPENDUID 1234567 13] (Success)"
});
mFolder.appendMessages(new Message[] {message});
mock.close();
assertEquals("13", message.getUid());
assertEquals(7, mFolder.getMessageCount());
}
}