IMAP ID
* Add IMAP ID command to all login sequences * Send generic information for now * Explicitly catch & discard parsing errors, since we really don't care if the command succeeds or not. * Unit tests Bug: 2332183
This commit is contained in:
parent
57bdd9e580
commit
468371917e
@ -42,6 +42,7 @@ import com.android.email.mail.transport.MailTransport;
|
||||
import com.beetstra.jutf7.CharsetProvider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Config;
|
||||
import android.util.Log;
|
||||
|
||||
@ -79,6 +80,9 @@ import javax.net.ssl.SSLException;
|
||||
*/
|
||||
public class ImapStore extends Store {
|
||||
|
||||
// Always check in FALSE
|
||||
private static final boolean DEBUG_FORCE_SEND_ID = false;
|
||||
|
||||
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN, Flag.FLAGGED };
|
||||
|
||||
private Transport mRootTransport;
|
||||
@ -86,6 +90,8 @@ public class ImapStore extends Store {
|
||||
private String mPassword;
|
||||
private String mLoginPhrase;
|
||||
private String mPathPrefix;
|
||||
private String mIdPhrase = null;
|
||||
private static String sImapId = null;
|
||||
|
||||
private LinkedList<ImapConnection> mConnections =
|
||||
new LinkedList<ImapConnection>();
|
||||
@ -108,7 +114,7 @@ public class ImapStore extends Store {
|
||||
*/
|
||||
public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks)
|
||||
throws MessagingException {
|
||||
return new ImapStore(uri);
|
||||
return new ImapStore(context, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,7 +127,7 @@ public class ImapStore extends Store {
|
||||
*
|
||||
* @param uriString the Uri containing information to configure this store
|
||||
*/
|
||||
private ImapStore(String uriString) throws MessagingException {
|
||||
private ImapStore(Context context, String uriString) throws MessagingException {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(uriString);
|
||||
@ -166,6 +172,15 @@ public class ImapStore extends Store {
|
||||
}
|
||||
|
||||
mModifiedUtf7Charset = new CharsetProvider().charsetForName("X-RFC-3501");
|
||||
|
||||
// Assign user-agent string (for RFC2971 ID command)
|
||||
String mUserAgent = getImapId(context);
|
||||
if (mUserAgent != null) {
|
||||
mIdPhrase = "ID (" + mUserAgent + ")";
|
||||
} else if (DEBUG_FORCE_SEND_ID) {
|
||||
mIdPhrase = "ID NIL";
|
||||
}
|
||||
// else: mIdPhrase = null, no ID will be emitted
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,6 +193,73 @@ public class ImapStore extends Store {
|
||||
mRootTransport = testTransport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return, or create and return, an string suitable for use in an IMAP ID message.
|
||||
* This is constructed similarly to the way the browser sets up its user-agent strings.
|
||||
* See RFC 2971 for more details. The output of this command will be a series of key-value
|
||||
* pairs delimited by spaces (there is no point in returning a structured result because
|
||||
* this will be sent as-is to the IMAP server). No tokens, parenthesis or "ID" are included,
|
||||
* because some connections may append additional values.
|
||||
*
|
||||
* The following IMAP ID keys may be included:
|
||||
* name Android package name of the program
|
||||
* os "android"
|
||||
* os-version "version; model; build-id"
|
||||
* vendor Vendor of the client/server
|
||||
*
|
||||
* @return a String for use in an IMAP ID message.
|
||||
*/
|
||||
public String getImapId(Context context) {
|
||||
synchronized (Email.class) {
|
||||
if (sImapId == null) {
|
||||
// "name" "com.android.email"
|
||||
StringBuffer sb = new StringBuffer("\"name\" \"");
|
||||
sb.append(context.getPackageName());
|
||||
sb.append("\"");
|
||||
|
||||
// "os" "android"
|
||||
sb.append(" \"os\" \"android\"");
|
||||
|
||||
// "os-version" "version; model; build-id"
|
||||
sb.append(" \"os-version\" \"");
|
||||
final String version = Build.VERSION.RELEASE;
|
||||
if (version.length() > 0) {
|
||||
sb.append(version);
|
||||
} else {
|
||||
// default to "1.0"
|
||||
sb.append("1.0");
|
||||
}
|
||||
// add the model (on release builds only)
|
||||
if ("REL".equals(Build.VERSION.CODENAME)) {
|
||||
final String model = Build.MODEL;
|
||||
if (model.length() > 0) {
|
||||
sb.append("; ");
|
||||
sb.append(model);
|
||||
}
|
||||
}
|
||||
// add the build ID or build #
|
||||
final String id = Build.ID;
|
||||
if (id.length() > 0) {
|
||||
sb.append("; ");
|
||||
sb.append(id);
|
||||
}
|
||||
sb.append("\"");
|
||||
|
||||
// "vendor" "the vendor"
|
||||
final String vendor = Build.MANUFACTURER;
|
||||
if (vendor.length() > 0) {
|
||||
sb.append(" \"vendor\" \"");
|
||||
sb.append(vendor);
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
sImapId = sb.toString();
|
||||
}
|
||||
}
|
||||
return sImapId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Folder getFolder(String name) throws MessagingException {
|
||||
ImapFolder folder;
|
||||
@ -191,7 +273,6 @@ public class ImapStore extends Store {
|
||||
return folder;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Folder[] getPersonalNamespaces() throws MessagingException {
|
||||
ImapConnection connection = getConnection();
|
||||
@ -1171,6 +1252,22 @@ public class ImapStore extends Store {
|
||||
}
|
||||
}
|
||||
|
||||
// Send user-agent in an RFC2971 ID command
|
||||
if (mIdPhrase != null) {
|
||||
try {
|
||||
executeSimpleCommand(mIdPhrase);
|
||||
} catch (ImapException ie) {
|
||||
// Log for debugging, but this is not a fatal problem.
|
||||
if (Config.LOGD && Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, ie.toString());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// Special case to handle malformed OK responses and ignore them.
|
||||
// A true IOException will recur on the following login steps
|
||||
// This can go away after the parser is fixed - see bug 2138981 for details
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO eventually we need to add additional authentication
|
||||
// options such as SASL
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.email.mail.store;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.mail.FetchProfile;
|
||||
import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Folder;
|
||||
@ -31,9 +32,11 @@ import com.android.email.mail.transport.MockTransport;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
@ -80,6 +83,92 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
||||
// 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
|
||||
*
|
||||
* Since this is build-specific, we mostly just ensure that the correct strings
|
||||
* are being generated, and non-empty, and (if possible) look for expected formatting.
|
||||
*/
|
||||
public void testImapId() {
|
||||
String id = mStore.getImapId(getContext());
|
||||
// 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
|
||||
// [i+1] = key
|
||||
// [i+2] = one or more spaces
|
||||
// [i+3] = value
|
||||
map.put(elements[i+1], elements[i+3]);
|
||||
i += 4;
|
||||
}
|
||||
|
||||
// Strings we'll expect to find:
|
||||
// name Android package name of the program
|
||||
// os "android"
|
||||
// os-version "version; model; build-id"
|
||||
// vendor Vendor of the client/server
|
||||
|
||||
String name = map.get("name");
|
||||
assertEquals(getContext().getPackageName(), name);
|
||||
String os = map.get("os");
|
||||
assertEquals("android", os);
|
||||
String osversion = map.get("os-version");
|
||||
assertNotNull(osversion);
|
||||
String vendor = map.get("vendor");
|
||||
assertNotNull(vendor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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\")",
|
||||
"1 OK"});
|
||||
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",
|
||||
"1 OK [ID] bad-char-%"});
|
||||
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[] {
|
||||
"1 BAD unknown command bad-char-%"});
|
||||
mFolder.open(OpenMode.READ_WRITE, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that ImapList object correctly returns an appropriate Date object
|
||||
* without throwning MessagingException when getKeyedDate() is called.
|
||||
@ -112,14 +201,6 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
||||
assertEquals(1230800400000L, result.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO: Test the operation of checkSettings()
|
||||
* TODO: Test small Store & Folder functions that manage folders & namespace
|
||||
@ -191,17 +272,29 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
||||
* @param mockTransport the mock transport we're using
|
||||
*/
|
||||
private void setupOpenFolder(MockTransport mockTransport) {
|
||||
setupOpenFolder(mockTransport, new String[] {
|
||||
"* ID NIL", "1 OK"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
|
||||
* Also allows setting a custom IMAP ID.
|
||||
*
|
||||
* @param mockTransport the mock transport we're using
|
||||
*/
|
||||
private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse) {
|
||||
mockTransport.expect(null, "* OK Imap 2000 Ready To Assist You");
|
||||
mockTransport.expect("1 LOGIN user \"password\"",
|
||||
"1 OK user authenticated (Success)");
|
||||
mockTransport.expect("2 SELECT \"INBOX\"", new String[] {
|
||||
mockTransport.expect("1 ID \\(.*\\)", imapIdResponse);
|
||||
mockTransport.expect("2 LOGIN user \"password\"",
|
||||
"2 OK user authenticated (Success)");
|
||||
mockTransport.expect("3 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]",
|
||||
"2 OK [READ-WRITE] INBOX selected. (Success)"});
|
||||
"3 OK [READ-WRITE] INBOX selected. (Success)"});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -210,9 +303,9 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
||||
public void testGetUnreadMessageCountWithQuotedString() throws Exception {
|
||||
MockTransport mock = openAndInjectMockTransport();
|
||||
setupOpenFolder(mock);
|
||||
mock.expect("3 STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
|
||||
mock.expect("4 STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
|
||||
"* STATUS \"INBOX\" (UNSEEN 2)",
|
||||
"3 OK STATUS completed"});
|
||||
"4 OK STATUS completed"});
|
||||
mFolder.open(OpenMode.READ_WRITE, null);
|
||||
int unreadCount = mFolder.getUnreadMessageCount();
|
||||
assertEquals("getUnreadMessageCount with quoted string", 2, unreadCount);
|
||||
@ -224,10 +317,10 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
||||
public void testGetUnreadMessageCountWithLiteralString() throws Exception {
|
||||
MockTransport mock = openAndInjectMockTransport();
|
||||
setupOpenFolder(mock);
|
||||
mock.expect("3 STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
|
||||
mock.expect("4 STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
|
||||
"* STATUS {5}",
|
||||
"INBOX (UNSEEN 10)",
|
||||
"3 OK STATUS completed"});
|
||||
"4 OK STATUS completed"});
|
||||
mFolder.open(OpenMode.READ_WRITE, null);
|
||||
int unreadCount = mFolder.getUnreadMessageCount();
|
||||
assertEquals("getUnreadMessageCount with literal string", 10, unreadCount);
|
||||
@ -246,9 +339,9 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
||||
FetchProfile fp = new FetchProfile();fp.clear();
|
||||
fp.add(FetchProfile.Item.STRUCTURE);
|
||||
Message message1 = mFolder.createMessage("1");
|
||||
mock.expect("3 UID FETCH 1 \\(UID BODYSTRUCTURE\\)", new String[] {
|
||||
mock.expect("4 UID FETCH 1 \\(UID BODYSTRUCTURE\\)", new String[] {
|
||||
"* 1 FETCH (UID 1 BODYSTRUCTURE (TEXT PLAIN NIL NIL NIL 7BIT 0 0 NIL NIL NIL))",
|
||||
"3 OK SUCCESS"
|
||||
"4 OK SUCCESS"
|
||||
});
|
||||
mFolder.fetch(new Message[] { message1 }, fp, null);
|
||||
|
||||
@ -259,9 +352,9 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
||||
// 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("4 UID FETCH 1 \\(UID BODY.PEEK\\[TEXT\\]\\)", new String[] {
|
||||
mock.expect("5 UID FETCH 1 \\(UID BODY.PEEK\\[TEXT\\]\\)", new String[] {
|
||||
"* 1 FETCH (UID 1 BODY[TEXT] NIL)",
|
||||
"4 OK SUCCESS"
|
||||
"5 OK SUCCESS"
|
||||
});
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
|
Loading…
Reference in New Issue
Block a user