Implement IMAP prefix support
We support two different ways for an IMAP prefix to be specified: 1. A text field on the IMAP configuration page. This is the most obvious to the end user. It is also an explicit, manual configuration. 2. RFC2342 defines a NAMESPACE IMAP command to be able to query the prefix from the IMAP server. This is an automatic configuration without any user involvement (i.e. the UI will NOT change if a prefix is loaded in this way) If the user goes to the trouble of specifying a prefix, we will always honour it instead of the namespace returned by the IMAP server -- even if the user's configuration is wrong. bug 1592696 Change-Id: I6b94c7aaac538f6cd9dc4694b0f1634e8c956bc1
This commit is contained in:
parent
da71042569
commit
32311cce01
|
@ -258,7 +258,7 @@ public class MessagingController implements Runnable {
|
|||
|
||||
Store store = Store.getInstance(account.getStoreUri(mContext), mContext, null);
|
||||
|
||||
Folder[] remoteFolders = store.getPersonalNamespaces();
|
||||
Folder[] remoteFolders = store.getAllFolders();
|
||||
|
||||
HashSet<String> remoteFolderNames = new HashSet<String>();
|
||||
for (int i = 0, count = remoteFolders.length; i < count; i++) {
|
||||
|
|
|
@ -245,7 +245,7 @@ public abstract class Store {
|
|||
|
||||
public abstract Folder getFolder(String name) throws MessagingException;
|
||||
|
||||
public abstract Folder[] getPersonalNamespaces() throws MessagingException;
|
||||
public abstract Folder[] getAllFolders() throws MessagingException;
|
||||
|
||||
public abstract Bundle checkSettings() throws MessagingException;
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ public class ExchangeStore extends Store {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Folder[] getPersonalNamespaces() {
|
||||
public Folder[] getAllFolders() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@ import android.telephony.TelephonyManager;
|
|||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Base64DataException;
|
||||
import android.util.Config;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -71,6 +70,7 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -111,9 +111,10 @@ public class ImapStore extends Store {
|
|||
private String mUsername;
|
||||
private String mPassword;
|
||||
private String mLoginPhrase;
|
||||
private String mPathPrefix;
|
||||
private String mIdPhrase = null;
|
||||
private static String sImapId = null;
|
||||
/*package*/ String mPathPrefix;
|
||||
/*package*/ String mPathSeparator;
|
||||
|
||||
private final ConcurrentLinkedQueue<ImapConnection> mConnectionPool =
|
||||
new ConcurrentLinkedQueue<ImapConnection>();
|
||||
|
@ -240,11 +241,11 @@ public class ImapStore extends Store {
|
|||
*
|
||||
* @param userName the username of the account
|
||||
* @param host the host (server) of the account
|
||||
* @param capabilityResponse the capabilities list from the server
|
||||
* @param capabilities a list of the capabilities from the server
|
||||
* @return a String for use in an IMAP ID message.
|
||||
*/
|
||||
/* package */ static String getImapId(Context context, String userName, String host,
|
||||
ImapResponse capabilityResponse) {
|
||||
String capabilities) {
|
||||
// The first section is global to all IMAP connections, and generates the fixed
|
||||
// values in any IMAP ID message
|
||||
synchronized (ImapStore.class) {
|
||||
|
@ -266,7 +267,7 @@ public class ImapStore extends Store {
|
|||
|
||||
// Optionally add any vendor-supplied id keys
|
||||
String vendorId = VendorPolicyLoader.getInstance(context).getImapIdValues(userName, host,
|
||||
capabilityResponse.flatten());
|
||||
capabilities);
|
||||
if (vendorId != null) {
|
||||
id.append(' ');
|
||||
id.append(vendorId);
|
||||
|
@ -372,7 +373,7 @@ public class ImapStore extends Store {
|
|||
|
||||
|
||||
@Override
|
||||
public Folder getFolder(String name) throws MessagingException {
|
||||
public Folder getFolder(String name) {
|
||||
ImapFolder folder;
|
||||
synchronized (mFolderCache) {
|
||||
folder = mFolderCache.get(name);
|
||||
|
@ -385,13 +386,18 @@ public class ImapStore extends Store {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Folder[] getPersonalNamespaces() throws MessagingException {
|
||||
public Folder[] getAllFolders() throws MessagingException {
|
||||
ImapConnection connection = getConnection();
|
||||
try {
|
||||
ArrayList<Folder> folders = new ArrayList<Folder>();
|
||||
List<ImapResponse> responses = connection.executeSimpleCommand(
|
||||
String.format(ImapConstants.LIST + " \"\" \"%s*\"",
|
||||
mPathPrefix == null ? "" : mPathPrefix));
|
||||
// Establish a connection to the IMAP server; if necessary
|
||||
// This ensures a valid prefix if the prefix is automatically set by the server
|
||||
connection.executeSimpleCommand(ImapConstants.NOOP);
|
||||
String imapCommand = ImapConstants.LIST + " \"\" \"*\"";
|
||||
if (mPathPrefix != null) {
|
||||
imapCommand = ImapConstants.LIST + " \"\" \"" + mPathPrefix + "*\"";
|
||||
}
|
||||
List<ImapResponse> responses = connection.executeSimpleCommand(imapCommand);
|
||||
for (ImapResponse response : responses) {
|
||||
// S: * LIST (\Noselect) "/" ~/Mail/foo
|
||||
if (response.isDataResponse(0, ImapConstants.LIST)) {
|
||||
|
@ -400,7 +406,7 @@ public class ImapStore extends Store {
|
|||
// Get folder name.
|
||||
ImapString encodedFolder = response.getStringOrEmpty(3);
|
||||
if (encodedFolder.isEmpty()) continue;
|
||||
String folder = decodeFolderName(encodedFolder.getString());
|
||||
String folder = decodeFolderName(encodedFolder.getString(), mPathPrefix);
|
||||
if (ImapConstants.INBOX.equalsIgnoreCase(folder)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -451,6 +457,19 @@ public class ImapStore extends Store {
|
|||
return bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the path prefix, if necessary. The path prefix must always end with the
|
||||
* path separator.
|
||||
*/
|
||||
/*package*/ void ensurePrefixIsValid() {
|
||||
// Make sure the path prefix ends with the path separator
|
||||
if (!TextUtils.isEmpty(mPathPrefix) && !TextUtils.isEmpty(mPathSeparator)) {
|
||||
if (!mPathPrefix.endsWith(mPathSeparator)) {
|
||||
mPathPrefix = mPathPrefix + mPathSeparator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a connection if one is available from the pool, or creates a new one if not.
|
||||
*/
|
||||
|
@ -485,21 +504,37 @@ public class ImapStore extends Store {
|
|||
}
|
||||
}
|
||||
|
||||
/* package */ static String encodeFolderName(String name) {
|
||||
/**
|
||||
* Prepends the folder name with the given prefix and UTF-7 encodes it.
|
||||
*/
|
||||
/* package */ static String encodeFolderName(String name, String prefix) {
|
||||
// do NOT add the prefix to the special name "INBOX"
|
||||
if (ImapConstants.INBOX.equalsIgnoreCase(name)) return name;
|
||||
|
||||
// Prepend prefix
|
||||
if (prefix != null) {
|
||||
name = prefix + name;
|
||||
}
|
||||
|
||||
// TODO bypass the conversion if name doesn't have special char.
|
||||
ByteBuffer bb = MODIFIED_UTF_7_CHARSET.encode(name);
|
||||
byte[] b = new byte[bb.limit()];
|
||||
bb.get(b);
|
||||
|
||||
return Utility.fromAscii(b);
|
||||
}
|
||||
|
||||
/* package */ static String decodeFolderName(String name) {
|
||||
/**
|
||||
* UTF-7 decodes the folder name and removes the given path prefix.
|
||||
*/
|
||||
/* package */ static String decodeFolderName(String name, String prefix) {
|
||||
// TODO bypass the conversion if name doesn't have special char.
|
||||
/*
|
||||
* Convert the encoded name to US-ASCII, then pass it through the modified UTF-7
|
||||
* decoder and return the Unicode String.
|
||||
*/
|
||||
return MODIFIED_UTF_7_CHARSET.decode(ByteBuffer.wrap(Utility.toAscii(name))).toString();
|
||||
String folder;
|
||||
folder = MODIFIED_UTF_7_CHARSET.decode(ByteBuffer.wrap(Utility.toAscii(name))).toString();
|
||||
if ((prefix != null) && folder.startsWith(prefix)) {
|
||||
folder = folder.substring(prefix.length());
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -526,7 +561,7 @@ public class ImapStore extends Store {
|
|||
private OpenMode mMode;
|
||||
private boolean mExists;
|
||||
|
||||
public ImapFolder(ImapStore store, String name) {
|
||||
/*package*/ ImapFolder(ImapStore store, String name) {
|
||||
mStore = store;
|
||||
mName = name;
|
||||
}
|
||||
|
@ -574,7 +609,7 @@ public class ImapStore extends Store {
|
|||
try {
|
||||
List<ImapResponse> responses = mConnection.executeSimpleCommand(
|
||||
String.format(ImapConstants.SELECT + " \"%s\"",
|
||||
encodeFolderName(mName)));
|
||||
encodeFolderName(mName, mStore.mPathPrefix)));
|
||||
/*
|
||||
* If the command succeeds we expect the folder has been opened read-write
|
||||
* unless we are notified otherwise in the responses.
|
||||
|
@ -628,7 +663,7 @@ public class ImapStore extends Store {
|
|||
}
|
||||
|
||||
@Override
|
||||
public OpenMode getMode() throws MessagingException {
|
||||
public OpenMode getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
|
@ -669,7 +704,7 @@ public class ImapStore extends Store {
|
|||
try {
|
||||
connection.executeSimpleCommand(String.format(
|
||||
ImapConstants.STATUS + " \"%s\" (" + ImapConstants.UIDVALIDITY + ")",
|
||||
encodeFolderName(mName)));
|
||||
encodeFolderName(mName, mStore.mPathPrefix)));
|
||||
mExists = true;
|
||||
return true;
|
||||
|
||||
|
@ -710,7 +745,7 @@ public class ImapStore extends Store {
|
|||
}
|
||||
try {
|
||||
connection.executeSimpleCommand(String.format(ImapConstants.CREATE + " \"%s\"",
|
||||
encodeFolderName(mName)));
|
||||
encodeFolderName(mName, mStore.mPathPrefix)));
|
||||
return true;
|
||||
|
||||
} catch (MessagingException me) {
|
||||
|
@ -735,7 +770,7 @@ public class ImapStore extends Store {
|
|||
mConnection.executeSimpleCommand(
|
||||
String.format(ImapConstants.UID_COPY + " %s \"%s\"",
|
||||
joinMessageUids(messages),
|
||||
encodeFolderName(folder.getName())));
|
||||
encodeFolderName(folder.getName(), mStore.mPathPrefix)));
|
||||
} catch (IOException ioe) {
|
||||
throw ioExceptionHandler(mConnection, ioe);
|
||||
} finally {
|
||||
|
@ -755,7 +790,7 @@ public class ImapStore extends Store {
|
|||
int unreadMessageCount = 0;
|
||||
List<ImapResponse> responses = mConnection.executeSimpleCommand(String.format(
|
||||
ImapConstants.STATUS + " \"%s\" (" + ImapConstants.UNSEEN + ")",
|
||||
encodeFolderName(mName)));
|
||||
encodeFolderName(mName, mStore.mPathPrefix)));
|
||||
// S: * STATUS mboxname (MESSAGES 231 UIDNEXT 44292)
|
||||
for (ImapResponse response : responses) {
|
||||
if (response.isDataResponse(0, ImapConstants.STATUS)) {
|
||||
|
@ -772,7 +807,7 @@ public class ImapStore extends Store {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void delete(boolean recurse) throws MessagingException {
|
||||
public void delete(boolean recurse) {
|
||||
throw new Error("ImapStore.delete() not yet implemented");
|
||||
}
|
||||
|
||||
|
@ -846,8 +881,7 @@ public class ImapStore extends Store {
|
|||
return getMessagesInternal(uids, listener);
|
||||
}
|
||||
|
||||
public Message[] getMessagesInternal(String[] uids, MessageRetrievalListener listener)
|
||||
throws MessagingException {
|
||||
public Message[] getMessagesInternal(String[] uids, MessageRetrievalListener listener) {
|
||||
final ArrayList<Message> messages = new ArrayList<Message>(uids.length);
|
||||
for (int i = 0; i < uids.length; i++) {
|
||||
final String uid = uids[i];
|
||||
|
@ -1061,7 +1095,7 @@ public class ImapStore extends Store {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Flag[] getPermanentFlags() throws MessagingException {
|
||||
public Flag[] getPermanentFlags() {
|
||||
return PERMANENT_FLAGS;
|
||||
}
|
||||
|
||||
|
@ -1295,7 +1329,7 @@ public class ImapStore extends Store {
|
|||
|
||||
mConnection.sendCommand(
|
||||
String.format(ImapConstants.APPEND + " \"%s\" (%s) {%d}",
|
||||
encodeFolderName(mName),
|
||||
encodeFolderName(mName, mStore.mPathPrefix),
|
||||
flagList,
|
||||
out.getCount()), false);
|
||||
ImapResponse response;
|
||||
|
@ -1403,8 +1437,7 @@ public class ImapStore extends Store {
|
|||
}
|
||||
}
|
||||
|
||||
private MessagingException ioExceptionHandler(ImapConnection connection, IOException ioe)
|
||||
throws MessagingException {
|
||||
private MessagingException ioExceptionHandler(ImapConnection connection, IOException ioe) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, "IO Exception detected: ", ioe);
|
||||
}
|
||||
|
@ -1426,7 +1459,7 @@ public class ImapStore extends Store {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(String uid) throws MessagingException {
|
||||
public Message createMessage(String uid) {
|
||||
return new ImapMessage(uid, this);
|
||||
}
|
||||
}
|
||||
|
@ -1462,70 +1495,39 @@ public class ImapStore extends Store {
|
|||
mParser.readResponse();
|
||||
|
||||
// CAPABILITY
|
||||
ImapResponse capabilityResponse = queryCapabilities();
|
||||
ImapResponse capabilities = queryCapabilities();
|
||||
|
||||
boolean hasStartTlsCapability =
|
||||
capabilities.contains(ImapConstants.STARTTLS);
|
||||
|
||||
// TLS
|
||||
if (mTransport.canTryTlsSecurity()) {
|
||||
if (capabilityResponse.contains(ImapConstants.STARTTLS)) {
|
||||
// STARTTLS
|
||||
executeSimpleCommand(ImapConstants.STARTTLS);
|
||||
|
||||
mTransport.reopenTls();
|
||||
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
||||
createParser();
|
||||
// Per RFC requirement (3501-6.2.1) gather new capabilities
|
||||
capabilityResponse = queryCapabilities();
|
||||
} else {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, "TLS not supported but required");
|
||||
}
|
||||
throw new MessagingException(MessagingException.TLS_REQUIRED);
|
||||
}
|
||||
ImapResponse newCapabilities = doStartTls(hasStartTlsCapability);
|
||||
if (newCapabilities != null) {
|
||||
capabilities = newCapabilities;
|
||||
}
|
||||
|
||||
// NOTE: An IMAP response MUST be processed before issuing any new IMAP
|
||||
// requests. Subsequent requests may destroy previous response data. As
|
||||
// such, we save away capability information here for future use.
|
||||
boolean hasIdCapability =
|
||||
capabilities.contains(ImapConstants.ID);
|
||||
boolean hasNamespaceCapability =
|
||||
capabilities.contains(ImapConstants.NAMESPACE);
|
||||
String capabilityString = capabilities.flatten();
|
||||
|
||||
// ID
|
||||
if (capabilityResponse.contains(ImapConstants.ID)) {
|
||||
// Assign user-agent string (for RFC2971 ID command)
|
||||
String mUserAgent = getImapId(mContext, mUsername, mRootTransport.getHost(),
|
||||
capabilityResponse);
|
||||
if (mUserAgent != null) {
|
||||
mIdPhrase = ImapConstants.ID + " (" + mUserAgent + ")";
|
||||
} else if (DEBUG_FORCE_SEND_ID) {
|
||||
mIdPhrase = ImapConstants.ID + " " + ImapConstants.NIL;
|
||||
}
|
||||
// else: mIdPhrase = null, no ID will be emitted
|
||||
|
||||
// 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 (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
|
||||
}
|
||||
}
|
||||
}
|
||||
doSendId(hasIdCapability, capabilityString);
|
||||
|
||||
// LOGIN
|
||||
try {
|
||||
// TODO eventually we need to add additional authentication
|
||||
// options such as SASL
|
||||
executeSimpleCommand(mLoginPhrase, true);
|
||||
} catch (ImapException ie) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, ie.toString());
|
||||
}
|
||||
throw new AuthenticationFailedException(ie.getAlertText(), ie);
|
||||
doLogin();
|
||||
|
||||
} catch (MessagingException me) {
|
||||
throw new AuthenticationFailedException(null, me);
|
||||
}
|
||||
// NAMESPACE (only valid in the Authenticated state)
|
||||
doGetNamespace(hasNamespaceCapability);
|
||||
|
||||
// Gets the path separator from the server
|
||||
doGetPathSeparator();
|
||||
|
||||
ensurePrefixIsValid();
|
||||
} catch (SSLException e) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, e.toString());
|
||||
|
@ -1595,12 +1597,12 @@ public class ImapStore extends Store {
|
|||
return tag;
|
||||
}
|
||||
|
||||
public List<ImapResponse> executeSimpleCommand(String command) throws IOException,
|
||||
/*package*/ List<ImapResponse> executeSimpleCommand(String command) throws IOException,
|
||||
MessagingException {
|
||||
return executeSimpleCommand(command, false);
|
||||
}
|
||||
|
||||
public List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
|
||||
/*package*/ List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
|
||||
throws IOException, MessagingException {
|
||||
String tag = sendCommand(command, sensitive);
|
||||
ArrayList<ImapResponse> responses = new ArrayList<ImapResponse>();
|
||||
|
@ -1635,14 +1637,161 @@ public class ImapStore extends Store {
|
|||
return capabilityResponse;
|
||||
}
|
||||
|
||||
/** @see ImapResponseParser#logLastDiscourse() */
|
||||
/**
|
||||
* Sends client identification information to the IMAP server per RFC 2971. If
|
||||
* the server does not support the ID command, this will perform no operation.
|
||||
*/
|
||||
private void doSendId(boolean hasIdCapability, String capabilities)
|
||||
throws MessagingException {
|
||||
if (!hasIdCapability) return;
|
||||
|
||||
// Assign user-agent string (for RFC2971 ID command)
|
||||
String mUserAgent =
|
||||
getImapId(mContext, mUsername, mRootTransport.getHost(), capabilities);
|
||||
|
||||
if (mUserAgent != null) {
|
||||
mIdPhrase = ImapConstants.ID + " (" + mUserAgent + ")";
|
||||
} else if (DEBUG_FORCE_SEND_ID) {
|
||||
mIdPhrase = ImapConstants.ID + " " + ImapConstants.NIL;
|
||||
}
|
||||
// else: mIdPhrase = null, no ID will be emitted
|
||||
|
||||
// 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 (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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's Personal Namespace from the IMAP server per RFC 2342. If the user
|
||||
* explicitly sets a namespace (using setup UI) or if the server does not support the
|
||||
* namespace command, this will perform no operation.
|
||||
*/
|
||||
private void doGetNamespace(boolean hasNamespaceCapability) throws MessagingException {
|
||||
// user did not specify a hard-coded prefix; try to get it from the server
|
||||
if (hasNamespaceCapability && TextUtils.isEmpty(mPathPrefix)) {
|
||||
List<ImapResponse> responseList = Collections.emptyList();
|
||||
|
||||
try {
|
||||
responseList = executeSimpleCommand(ImapConstants.NAMESPACE);
|
||||
} catch (ImapException ie) {
|
||||
// Log for debugging, but this is not a fatal problem.
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, ie.toString());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// Special case to handle malformed OK responses and ignore them.
|
||||
}
|
||||
|
||||
for (ImapResponse response: responseList) {
|
||||
if (response.isDataResponse(0, ImapConstants.NAMESPACE)) {
|
||||
ImapList namespaceList = response.getListOrEmpty(1);
|
||||
ImapList namespace = namespaceList.getListOrEmpty(0);
|
||||
String namespaceString = namespace.getStringOrEmpty(0).getString();
|
||||
if (!TextUtils.isEmpty(namespaceString)) {
|
||||
mPathPrefix = decodeFolderName(namespaceString, null);
|
||||
mPathSeparator = namespace.getStringOrEmpty(1).getString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs into the IMAP server
|
||||
*/
|
||||
private void doLogin()
|
||||
throws IOException, MessagingException, AuthenticationFailedException {
|
||||
try {
|
||||
// TODO eventually we need to add additional authentication
|
||||
// options such as SASL
|
||||
executeSimpleCommand(mLoginPhrase, true);
|
||||
} catch (ImapException ie) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, ie.toString());
|
||||
}
|
||||
throw new AuthenticationFailedException(ie.getAlertText(), ie);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
throw new AuthenticationFailedException(null, me);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path separator per the LIST command in RFC 3501. If the path separator
|
||||
* was obtained while obtaining the namespace or there is no prefix defined, this
|
||||
* will perform no operation.
|
||||
*/
|
||||
private void doGetPathSeparator() throws MessagingException {
|
||||
// user did not specify a hard-coded prefix; try to get it from the server
|
||||
if (TextUtils.isEmpty(mPathSeparator) && !TextUtils.isEmpty(mPathPrefix)) {
|
||||
List<ImapResponse> responseList = Collections.emptyList();
|
||||
|
||||
try {
|
||||
responseList = executeSimpleCommand(ImapConstants.LIST + " \"\" \"\"");
|
||||
} catch (ImapException ie) {
|
||||
// Log for debugging, but this is not a fatal problem.
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, ie.toString());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// Special case to handle malformed OK responses and ignore them.
|
||||
}
|
||||
|
||||
for (ImapResponse response: responseList) {
|
||||
if (response.isDataResponse(0, ImapConstants.LIST)) {
|
||||
mPathSeparator = response.getStringOrEmpty(2).getString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a TLS session with the IMAP server per RFC 3501. If the user has not opted
|
||||
* to use TLS or the server does not support the TLS capability, this will perform
|
||||
* no operation.
|
||||
*/
|
||||
private ImapResponse doStartTls(boolean hasStartTlsCapability)
|
||||
throws IOException, MessagingException {
|
||||
if (mTransport.canTryTlsSecurity()) {
|
||||
if (hasStartTlsCapability) {
|
||||
// STARTTLS
|
||||
executeSimpleCommand(ImapConstants.STARTTLS);
|
||||
|
||||
mTransport.reopenTls();
|
||||
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
||||
createParser();
|
||||
// Per RFC requirement (3501-6.2.1) gather new capabilities
|
||||
return(queryCapabilities());
|
||||
} else {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, "TLS not supported but required");
|
||||
}
|
||||
throw new MessagingException(MessagingException.TLS_REQUIRED);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @see DiscourseLogger#logLastDiscourse() */
|
||||
public void logLastDiscourse() {
|
||||
mDiscourse.logLastDiscourse();
|
||||
}
|
||||
}
|
||||
|
||||
static class ImapMessage extends MimeMessage {
|
||||
ImapMessage(String uid, Folder folder) throws MessagingException {
|
||||
ImapMessage(String uid, Folder folder) {
|
||||
this.mUid = uid;
|
||||
this.mFolder = folder;
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ public class Pop3Store extends Store {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Folder[] getPersonalNamespaces() throws MessagingException {
|
||||
public Folder[] getAllFolders() throws MessagingException {
|
||||
return new Folder[] {
|
||||
getFolder("INBOX"),
|
||||
};
|
||||
|
|
|
@ -60,6 +60,7 @@ public final class ImapConstants {
|
|||
public static final String LOGIN = "LOGIN";
|
||||
public static final String LOGOUT = "LOGOUT";
|
||||
public static final String LSUB = "LSUB";
|
||||
public static final String NAMESPACE = "NAMESPACE";
|
||||
public static final String NO = "NO";
|
||||
public static final String NOOP = "NOOP";
|
||||
public static final String OK = "OK";
|
||||
|
|
|
@ -24,7 +24,6 @@ import com.android.email.mail.transport.LoggingInputStream;
|
|||
import com.android.emailcommon.mail.MessagingException;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Config;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -236,7 +235,7 @@ public class ImapResponseParser {
|
|||
/**
|
||||
* Read all bytes until \r\n.
|
||||
*/
|
||||
/* package */ String readUntilEol() throws IOException, MessagingException {
|
||||
/* package */ String readUntilEol() throws IOException {
|
||||
String ret = readUntil('\r');
|
||||
expect('\n'); // TODO Should this really be error?
|
||||
return ret;
|
||||
|
|
|
@ -191,7 +191,7 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
// x-android-net-operator Carrier (Unreliable, so not tested here)
|
||||
// AGUID A device+account UID
|
||||
String id = ImapStore.getImapId(getContext(), "user-name", "host-name",
|
||||
CAPABILITY_RESPONSE);
|
||||
CAPABILITY_RESPONSE.flatten());
|
||||
HashMap<String, String> map = tokenizeImapId(id);
|
||||
assertEquals(getContext().getPackageName(), map.get("name"));
|
||||
assertEquals("android", map.get("os"));
|
||||
|
@ -242,7 +242,7 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
|
||||
// Invoke
|
||||
String id = ImapStore.getImapId(getContext(), "user-name", "host-name",
|
||||
ImapTestUtils.parseResponse("* CAPABILITY IMAP4rev1 XXX YYY Z"));
|
||||
ImapTestUtils.parseResponse("* CAPABILITY IMAP4rev1 XXX YYY Z").flatten());
|
||||
|
||||
// Check the result
|
||||
assertEquals("test-value", tokenizeImapId(id).get("test-key"));
|
||||
|
@ -292,9 +292,10 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
ImapStore store2 = (ImapStore) ImapStore.newInstance("imap://user2:password@server:999",
|
||||
getContext(), null);
|
||||
|
||||
String id1a = ImapStore.getImapId(getContext(), "user1", "host-name", CAPABILITY_RESPONSE);
|
||||
String id1b = ImapStore.getImapId(getContext(), "user1", "host-name", CAPABILITY_RESPONSE);
|
||||
String id2 = ImapStore.getImapId(getContext(), "user2", "host-name", CAPABILITY_RESPONSE);
|
||||
String capabilities = CAPABILITY_RESPONSE.flatten();
|
||||
String id1a = ImapStore.getImapId(getContext(), "user1", "host-name", capabilities);
|
||||
String id1b = ImapStore.getImapId(getContext(), "user1", "host-name", capabilities);
|
||||
String id2 = ImapStore.getImapId(getContext(), "user2", "host-name", capabilities);
|
||||
|
||||
String uid1a = tokenizeImapId(id1a).get("AGUID");
|
||||
String uid1b = tokenizeImapId(id1b).get("AGUID");
|
||||
|
@ -382,7 +383,7 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
/**
|
||||
* Test small Folder functions that don't really do anything in Imap
|
||||
*/
|
||||
public void testSmallFolderFunctions() throws MessagingException {
|
||||
public void testSmallFolderFunctions() {
|
||||
// getPermanentFlags() returns { Flag.DELETED, Flag.SEEN, Flag.FLAGGED }
|
||||
Flag[] flags = mFolder.getPermanentFlags();
|
||||
assertEquals(3, flags.length);
|
||||
|
@ -1096,10 +1097,11 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
assertEquals("initial uid", message.getUid());
|
||||
}
|
||||
|
||||
public void testGetPersonalNamespaces() throws Exception {
|
||||
public void testGetAllFolders() throws Exception {
|
||||
MockTransport mock = openAndInjectMockTransport();
|
||||
expectLogin(mock);
|
||||
|
||||
expectNoop(mock, true);
|
||||
mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"",
|
||||
new String[] {
|
||||
"* lIST (\\HAsNoChildren) \"/\" \"inbox\"",
|
||||
|
@ -1108,7 +1110,7 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
"* lIST (\\HAsNoChildren) \"/\" \"&ZeVnLIqe-\"", // Japanese folder name
|
||||
getNextTag(true) + " oK SUCCESS"
|
||||
});
|
||||
Folder[] folders = mStore.getPersonalNamespaces();
|
||||
Folder[] folders = mStore.getAllFolders();
|
||||
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
for (Folder f : folders) {
|
||||
|
@ -1124,19 +1126,155 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
}
|
||||
|
||||
public void testEncodeFolderName() {
|
||||
assertEquals("", ImapStore.encodeFolderName(""));
|
||||
assertEquals("a", ImapStore.encodeFolderName("a"));
|
||||
assertEquals("XYZ", ImapStore.encodeFolderName("XYZ"));
|
||||
assertEquals("&ZeVnLIqe-", ImapStore.encodeFolderName("\u65E5\u672C\u8A9E"));
|
||||
assertEquals("!&ZeVnLIqe-!", ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!"));
|
||||
// null prefix
|
||||
assertEquals("",
|
||||
ImapStore.encodeFolderName("", null));
|
||||
assertEquals("a",
|
||||
ImapStore.encodeFolderName("a", null));
|
||||
assertEquals("XYZ",
|
||||
ImapStore.encodeFolderName("XYZ", null));
|
||||
assertEquals("&ZeVnLIqe-",
|
||||
ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", null));
|
||||
assertEquals("!&ZeVnLIqe-!",
|
||||
ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", null));
|
||||
// empty prefix (same as a null prefix)
|
||||
assertEquals("",
|
||||
ImapStore.encodeFolderName("", ""));
|
||||
assertEquals("a",
|
||||
ImapStore.encodeFolderName("a", ""));
|
||||
assertEquals("XYZ",
|
||||
ImapStore.encodeFolderName("XYZ", ""));
|
||||
assertEquals("&ZeVnLIqe-",
|
||||
ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", ""));
|
||||
assertEquals("!&ZeVnLIqe-!",
|
||||
ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", ""));
|
||||
// defined prefix
|
||||
assertEquals("[Gmail]/",
|
||||
ImapStore.encodeFolderName("", "[Gmail]/"));
|
||||
assertEquals("[Gmail]/a",
|
||||
ImapStore.encodeFolderName("a", "[Gmail]/"));
|
||||
assertEquals("[Gmail]/XYZ",
|
||||
ImapStore.encodeFolderName("XYZ", "[Gmail]/"));
|
||||
assertEquals("[Gmail]/&ZeVnLIqe-",
|
||||
ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", "[Gmail]/"));
|
||||
assertEquals("[Gmail]/!&ZeVnLIqe-!",
|
||||
ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", "[Gmail]/"));
|
||||
// Add prefix to special mailbox "INBOX" [case insensitive), no affect
|
||||
assertEquals("INBOX",
|
||||
ImapStore.encodeFolderName("INBOX", "[Gmail]/"));
|
||||
assertEquals("inbox",
|
||||
ImapStore.encodeFolderName("inbox", "[Gmail]/"));
|
||||
assertEquals("InBoX",
|
||||
ImapStore.encodeFolderName("InBoX", "[Gmail]/"));
|
||||
}
|
||||
|
||||
public void testDecodeFolderName() {
|
||||
assertEquals("", ImapStore.decodeFolderName(""));
|
||||
assertEquals("a", ImapStore.decodeFolderName("a"));
|
||||
assertEquals("XYZ", ImapStore.decodeFolderName("XYZ"));
|
||||
assertEquals("\u65E5\u672C\u8A9E", ImapStore.decodeFolderName("&ZeVnLIqe-"));
|
||||
assertEquals("!\u65E5\u672C\u8A9E!", ImapStore.decodeFolderName("!&ZeVnLIqe-!"));
|
||||
// null prefix
|
||||
assertEquals("",
|
||||
ImapStore.decodeFolderName("", null));
|
||||
assertEquals("a",
|
||||
ImapStore.decodeFolderName("a", null));
|
||||
assertEquals("XYZ",
|
||||
ImapStore.decodeFolderName("XYZ", null));
|
||||
assertEquals("\u65E5\u672C\u8A9E",
|
||||
ImapStore.decodeFolderName("&ZeVnLIqe-", null));
|
||||
assertEquals("!\u65E5\u672C\u8A9E!",
|
||||
ImapStore.decodeFolderName("!&ZeVnLIqe-!", null));
|
||||
// empty prefix (same as a null prefix)
|
||||
assertEquals("",
|
||||
ImapStore.decodeFolderName("", ""));
|
||||
assertEquals("a",
|
||||
ImapStore.decodeFolderName("a", ""));
|
||||
assertEquals("XYZ",
|
||||
ImapStore.decodeFolderName("XYZ", ""));
|
||||
assertEquals("\u65E5\u672C\u8A9E",
|
||||
ImapStore.decodeFolderName("&ZeVnLIqe-", ""));
|
||||
assertEquals("!\u65E5\u672C\u8A9E!",
|
||||
ImapStore.decodeFolderName("!&ZeVnLIqe-!", ""));
|
||||
// defined prefix; prefix found, prefix removed
|
||||
assertEquals("",
|
||||
ImapStore.decodeFolderName("[Gmail]/", "[Gmail]/"));
|
||||
assertEquals("a",
|
||||
ImapStore.decodeFolderName("[Gmail]/a", "[Gmail]/"));
|
||||
assertEquals("XYZ",
|
||||
ImapStore.decodeFolderName("[Gmail]/XYZ", "[Gmail]/"));
|
||||
assertEquals("\u65E5\u672C\u8A9E",
|
||||
ImapStore.decodeFolderName("[Gmail]/&ZeVnLIqe-", "[Gmail]/"));
|
||||
assertEquals("!\u65E5\u672C\u8A9E!",
|
||||
ImapStore.decodeFolderName("[Gmail]/!&ZeVnLIqe-!", "[Gmail]/"));
|
||||
// defined prefix; prefix not found, no affect
|
||||
assertEquals("INBOX/",
|
||||
ImapStore.decodeFolderName("INBOX/", "[Gmail]/"));
|
||||
assertEquals("INBOX/a",
|
||||
ImapStore.decodeFolderName("INBOX/a", "[Gmail]/"));
|
||||
assertEquals("INBOX/XYZ",
|
||||
ImapStore.decodeFolderName("INBOX/XYZ", "[Gmail]/"));
|
||||
assertEquals("INBOX/\u65E5\u672C\u8A9E",
|
||||
ImapStore.decodeFolderName("INBOX/&ZeVnLIqe-", "[Gmail]/"));
|
||||
assertEquals("INBOX/!\u65E5\u672C\u8A9E!",
|
||||
ImapStore.decodeFolderName("INBOX/!&ZeVnLIqe-!", "[Gmail]/"));
|
||||
}
|
||||
|
||||
public void testEnsurePrefixIsValid() {
|
||||
// Test mPathSeparator == null
|
||||
mStore.mPathSeparator = null;
|
||||
mStore.mPathPrefix = null;
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertNull(mStore.mPathPrefix);
|
||||
|
||||
mStore.mPathPrefix = "";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("", mStore.mPathPrefix);
|
||||
|
||||
mStore.mPathPrefix = "foo";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("foo", mStore.mPathPrefix);
|
||||
|
||||
mStore.mPathPrefix = "foo.";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("foo.", mStore.mPathPrefix);
|
||||
|
||||
// Test mPathSeparator == ""
|
||||
mStore.mPathSeparator = "";
|
||||
mStore.mPathPrefix = null;
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertNull(mStore.mPathPrefix);
|
||||
|
||||
mStore.mPathPrefix = "";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("", mStore.mPathPrefix);
|
||||
|
||||
mStore.mPathPrefix = "foo";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("foo", mStore.mPathPrefix);
|
||||
|
||||
mStore.mPathPrefix = "foo.";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("foo.", mStore.mPathPrefix);
|
||||
|
||||
// Test mPathSeparator is non-empty
|
||||
mStore.mPathSeparator = ".";
|
||||
mStore.mPathPrefix = null;
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertNull(mStore.mPathPrefix);
|
||||
|
||||
mStore.mPathPrefix = "";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("", mStore.mPathPrefix);
|
||||
|
||||
mStore.mPathPrefix = "foo";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("foo.", mStore.mPathPrefix);
|
||||
|
||||
// Trailing separator; path separator NOT appended
|
||||
mStore.mPathPrefix = "foo.";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("foo.", mStore.mPathPrefix);
|
||||
|
||||
// Trailing punctuation has no affect; path separator still appended
|
||||
mStore.mPathPrefix = "foo/";
|
||||
mStore.ensurePrefixIsValid();
|
||||
assertEquals("foo/.", mStore.mPathPrefix);
|
||||
}
|
||||
|
||||
public void testOpen() throws Exception {
|
||||
|
@ -1641,13 +1779,14 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
expectLogin(mock);
|
||||
|
||||
// List folders.
|
||||
expectNoop(mock, true);
|
||||
mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"",
|
||||
new String[] {
|
||||
"* LIST () \"/\" \"" + FOLDER_1 + "\"",
|
||||
"* LIST () \"/\" \"" + FOLDER_2 + "\"",
|
||||
getNextTag(true) + " OK SUCCESS"
|
||||
});
|
||||
final Folder[] folders = mStore.getPersonalNamespaces();
|
||||
"* LIST () \"/\" \"" + FOLDER_1 + "\"",
|
||||
"* LIST () \"/\" \"" + FOLDER_2 + "\"",
|
||||
getNextTag(true) + " OK SUCCESS"
|
||||
});
|
||||
final Folder[] folders = mStore.getAllFolders();
|
||||
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
for (Folder f : folders) {
|
||||
|
|
|
@ -241,7 +241,7 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
|
|||
public void testStoreFoldersFunctions() throws MessagingException {
|
||||
|
||||
// getPersonalNamespaces() always returns INBOX folder
|
||||
Folder[] folders = mStore.getPersonalNamespaces();
|
||||
Folder[] folders = mStore.getAllFolders();
|
||||
assertEquals(1, folders.length);
|
||||
assertSame(mFolder, folders[0]);
|
||||
|
||||
|
@ -319,7 +319,7 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
|
|||
* Lightweight test to confirm that POP3 hasn't implemented any folder roles yet.
|
||||
*/
|
||||
public void testNoFolderRolesYet() throws MessagingException {
|
||||
Folder[] remoteFolders = mStore.getPersonalNamespaces();
|
||||
Folder[] remoteFolders = mStore.getAllFolders();
|
||||
for (Folder folder : remoteFolders) {
|
||||
assertEquals(Folder.FolderRole.UNKNOWN, folder.getRole());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue