From 47762a9baa659c05c8f06494f88f123cbd2a7162 Mon Sep 17 00:00:00 2001 From: Fiouz Date: Thu, 30 Dec 2010 21:57:59 +0000 Subject: [PATCH] Remove coupling between ImapStore and ImapConnection by adding an settings indirection interface. Purpose: use the IMAP connection code to validate credentials without having an actual ImapStore instance. --- src/com/fsck/k9/mail/store/ImapStore.java | 299 +++++++++++++----- .../k9/mail/transport/imap/ImapSettings.java | 38 +++ 2 files changed, 253 insertions(+), 84 deletions(-) create mode 100644 src/com/fsck/k9/mail/transport/imap/ImapSettings.java diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 242ac5cd3..29b15012a 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -1,41 +1,21 @@ package com.fsck.k9.mail.store; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.PowerManager; -import android.util.Log; -import com.fsck.k9.Account; -import com.fsck.k9.K9; -import com.fsck.k9.R; -import com.fsck.k9.controller.MessageRetrievalListener; -import com.fsck.k9.helper.Utility; -import com.fsck.k9.helper.power.TracingPowerManager; -import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; -import com.fsck.k9.mail.*; -import com.fsck.k9.mail.filter.CountingOutputStream; -import com.fsck.k9.mail.filter.EOLConvertingOutputStream; -import com.fsck.k9.mail.filter.FixedLengthInputStream; -import com.fsck.k9.mail.filter.PeekableInputStream; -import com.fsck.k9.mail.internet.*; -import com.fsck.k9.mail.store.ImapResponseParser.ImapList; -import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse; -import com.jcraft.jzlib.JZlib; -import com.jcraft.jzlib.ZInputStream; -import com.jcraft.jzlib.ZOutputStream; -import com.beetstra.jutf7.CharsetProvider; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.TrustManager; - - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.binary.Hex; - -import java.io.*; -import java.net.*; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -45,11 +25,73 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Security; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManager; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.PowerManager; +import android.util.Log; + +import com.beetstra.jutf7.CharsetProvider; +import com.fsck.k9.Account; +import com.fsck.k9.K9; +import com.fsck.k9.R; +import com.fsck.k9.controller.MessageRetrievalListener; +import com.fsck.k9.helper.Utility; +import com.fsck.k9.helper.power.TracingPowerManager; +import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; +import com.fsck.k9.mail.AuthenticationFailedException; +import com.fsck.k9.mail.Body; +import com.fsck.k9.mail.CertificateValidationException; +import com.fsck.k9.mail.FetchProfile; +import com.fsck.k9.mail.Flag; +import com.fsck.k9.mail.Folder; +import com.fsck.k9.mail.Message; +import com.fsck.k9.mail.MessagingException; +import com.fsck.k9.mail.Part; +import com.fsck.k9.mail.PushReceiver; +import com.fsck.k9.mail.Pusher; +import com.fsck.k9.mail.Store; +import com.fsck.k9.mail.filter.CountingOutputStream; +import com.fsck.k9.mail.filter.EOLConvertingOutputStream; +import com.fsck.k9.mail.filter.FixedLengthInputStream; +import com.fsck.k9.mail.filter.PeekableInputStream; +import com.fsck.k9.mail.internet.MimeBodyPart; +import com.fsck.k9.mail.internet.MimeHeader; +import com.fsck.k9.mail.internet.MimeMessage; +import com.fsck.k9.mail.internet.MimeMultipart; +import com.fsck.k9.mail.internet.MimeUtility; +import com.fsck.k9.mail.store.ImapResponseParser.ImapList; +import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse; +import com.fsck.k9.mail.transport.imap.ImapSettings; +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZInputStream; +import com.jcraft.jzlib.ZOutputStream; + /** *
  * TODO Need to start keeping track of UIDVALIDITY
@@ -64,7 +106,7 @@ public class ImapStore extends Store
     public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
     public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
 
-    private enum AuthType { PLAIN, CRAM_MD5 }
+    public enum AuthType { PLAIN, CRAM_MD5 }
 
     private static final int IDLE_READ_TIMEOUT_INCREMENT = 5 * 60 * 1000;
     private static final int IDLE_FAILURE_COUNT_LIMIT = 10;
@@ -100,6 +142,89 @@ public class ImapStore extends Store
     private volatile String mCombinedPrefix = null;
     private volatile String mPathDelimeter = null;
 
+    public class StoreImapSettings implements ImapSettings
+    {
+
+        @Override
+        public String getHost()
+        {
+            return mHost;
+        }
+
+        @Override
+        public int getPort()
+        {
+            return mPort;
+        }
+
+        @Override
+        public int getConnectionSecurity()
+        {
+            return mConnectionSecurity;
+        }
+
+        @Override
+        public AuthType getAuthType()
+        {
+            return mAuthType;
+        }
+
+        @Override
+        public String getUsername()
+        {
+            return mUsername;
+        }
+
+        @Override
+        public String getPassword()
+        {
+            return mPassword;
+        }
+
+        @Override
+        public boolean useCompression(final int type)
+        {
+            return mAccount.useCompression(type);
+        }
+
+        @Override
+        public String getPathPrefix()
+        {
+            return mPathPrefix;
+        }
+
+        @Override
+        public void setPathPrefix(String prefix)
+        {
+            mPathPrefix = prefix;
+        }
+
+        @Override
+        public String getPathDelimeter()
+        {
+            return mPathDelimeter;
+        }
+
+        @Override
+        public void setPathDelimeter(String delimeter)
+        {
+            mPathDelimeter = delimeter;
+        }
+
+        @Override
+        public String getCombinedPrefix()
+        {
+            return mCombinedPrefix;
+        }
+
+        @Override
+        public void setCombinedPrefix(String prefix)
+        {
+            mCombinedPrefix = prefix;
+        }
+        
+    }
+
     private static final SimpleDateFormat RFC3501_DATE = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
 
     private LinkedList mConnections =
@@ -375,7 +500,7 @@ public class ImapStore extends Store
     {
         try
         {
-            ImapConnection connection = new ImapConnection();
+            ImapConnection connection = new ImapConnection(new StoreImapSettings());
             connection.open();
             connection.close();
         }
@@ -408,7 +533,7 @@ public class ImapStore extends Store
             }
             if (connection == null)
             {
-                connection = new ImapConnection();
+                connection = new ImapConnection(new StoreImapSettings());
             }
             return connection;
         }
@@ -2061,16 +2186,23 @@ public class ImapStore extends Store
     /**
      * A cacheable class that stores the details for a single IMAP connection.
      */
-    class ImapConnection
+    public static class ImapConnection
     {
-        private Socket mSocket;
-        private PeekableInputStream mIn;
-        private OutputStream mOut;
-        private ImapResponseParser mParser;
-        private int mNextCommandTag;
+        protected Socket mSocket;
+        protected PeekableInputStream mIn;
+        protected OutputStream mOut;
+        protected ImapResponseParser mParser;
+        protected int mNextCommandTag;
         protected Set capabilities = new HashSet();
 
-        private String getLogId()
+        private ImapSettings mSettings;
+
+        public ImapConnection(final ImapSettings settings)
+        {
+            this.mSettings = settings;
+        }
+
+        protected String getLogId()
         {
             return "conn" + hashCode();
         }
@@ -2126,7 +2258,6 @@ public class ImapStore extends Store
             return responses;
         }
 
-
         public void open() throws IOException, MessagingException
         {
             if (isOpen())
@@ -2161,19 +2292,19 @@ public class ImapStore extends Store
             try
             {
 
-                SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
+                SocketAddress socketAddress = new InetSocketAddress(mSettings.getHost(), mSettings.getPort());
 
                 if (K9.DEBUG)
-                    Log.i(K9.LOG_TAG, "Connection " + getLogId() + " connecting to " + mHost + " @ IP addr " + socketAddress);
+                    Log.i(K9.LOG_TAG, "Connection " + getLogId() + " connecting to " + mSettings.getHost() + " @ IP addr " + socketAddress);
 
-                if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
-                        mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL)
+                if (mSettings.getConnectionSecurity() == CONNECTION_SECURITY_SSL_REQUIRED ||
+                        mSettings.getConnectionSecurity() == CONNECTION_SECURITY_SSL_OPTIONAL)
                 {
                     SSLContext sslContext = SSLContext.getInstance("TLS");
-                    final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
+                    final boolean secure = mSettings.getConnectionSecurity() == CONNECTION_SECURITY_SSL_REQUIRED;
                     sslContext.init(null, new TrustManager[]
                                     {
-                                        TrustManagerFactory.get(mHost, secure)
+                                        TrustManagerFactory.get(mSettings.getHost(), secure)
                                     }, new SecureRandom());
                     mSocket = sslContext.getSocketFactory().createSocket();
                     mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
@@ -2211,8 +2342,8 @@ public class ImapStore extends Store
                     }
                 }
 
-                if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL
-                        || mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED)
+                if (mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_OPTIONAL
+                        || mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_REQUIRED)
                 {
 
                     if (hasCapability("STARTTLS"))
@@ -2221,12 +2352,12 @@ public class ImapStore extends Store
                         executeSimpleCommand("STARTTLS");
 
                         SSLContext sslContext = SSLContext.getInstance("TLS");
-                        boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
+                        boolean secure = mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_REQUIRED;
                         sslContext.init(null, new TrustManager[]
                                         {
-                                            TrustManagerFactory.get(mHost, secure)
+                                            TrustManagerFactory.get(mSettings.getHost(), secure)
                                         }, new SecureRandom());
-                        mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort,
+                        mSocket = sslContext.getSocketFactory().createSocket(mSocket, mSettings.getHost(), mSettings.getPort(),
                                   true);
                         mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
                         mIn = new PeekableInputStream(new BufferedInputStream(mSocket
@@ -2234,7 +2365,7 @@ public class ImapStore extends Store
                         mParser = new ImapResponseParser(mIn);
                         mOut = mSocket.getOutputStream();
                     }
-                    else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED)
+                    else if (mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_REQUIRED)
                     {
                         throw new MessagingException("TLS not supported but required");
                     }
@@ -2245,13 +2376,13 @@ public class ImapStore extends Store
                 try
                 {
                     // Yahoo! requires a custom IMAP command to work right over a non-3G network
-                    if (mHost.endsWith("yahoo.com"))
+                    if (mSettings.getHost().endsWith("yahoo.com"))
                     {
                         if (K9.DEBUG)
                             Log.v(K9.LOG_TAG, "Found Yahoo! account.  Sending proprietary commands.");
                         executeSimpleCommand("ID (\"GUID\" \"1\")");
                     }
-                    if (mAuthType == AuthType.CRAM_MD5)
+                    if (mSettings.getAuthType() == AuthType.CRAM_MD5)
                     {
                         authCramMD5();
                         // The authCramMD5 method called on the previous line does not allow for handling updated capabilities
@@ -2266,9 +2397,9 @@ public class ImapStore extends Store
                         }
 
                     }
-                    else if (mAuthType == AuthType.PLAIN)
+                    else if (mSettings.getAuthType() == AuthType.PLAIN)
                     {
-                        receiveCapabilities(executeSimpleCommand("LOGIN \"" + escapeString(mUsername) + "\" \"" + escapeString(mPassword) + "\"", true));
+                        receiveCapabilities(executeSimpleCommand("LOGIN \"" + escapeString(mSettings.getUsername()) + "\" \"" + escapeString(mSettings.getPassword()) + "\"", true));
                     }
                     authSuccess = true;
                 }
@@ -2296,7 +2427,7 @@ public class ImapStore extends Store
                         int type = netInfo.getType();
                         if (K9.DEBUG)
                             Log.d(K9.LOG_TAG, "On network type " + type);
-                        useCompression = mAccount.useCompression(type);
+                        useCompression = mSettings.useCompression(type);
 
                     }
                     if (K9.DEBUG)
@@ -2328,9 +2459,9 @@ public class ImapStore extends Store
 
                 if (K9.DEBUG)
                     Log.d(K9.LOG_TAG, "NAMESPACE = " + hasCapability(CAPABILITY_NAMESPACE)
-                          + ", mPathPrefix = " + mPathPrefix);
+                          + ", mPathPrefix = " + mSettings.getPathPrefix());
 
-                if (mPathPrefix == null)
+                if (mSettings.getPathPrefix() == null)
                 {
                     if (hasCapability(CAPABILITY_NAMESPACE))
                     {
@@ -2357,11 +2488,11 @@ public class ImapStore extends Store
                                         if (K9.DEBUG)
                                             Log.d(K9.LOG_TAG, "Got first personal namespaces: " + firstNamespace);
                                         bracketed = (ImapList)firstNamespace;
-                                        mPathPrefix = bracketed.getString(0);
-                                        mPathDelimeter = bracketed.getString(1);
-                                        mCombinedPrefix = null;
+                                        mSettings.setPathPrefix(bracketed.getString(0));
+                                        mSettings.setPathDelimeter(bracketed.getString(1));
+                                        mSettings.setCombinedPrefix(null);
                                         if (K9.DEBUG)
-                                            Log.d(K9.LOG_TAG, "Got path '" + mPathPrefix + "' and separator '" + mPathDelimeter + "'");
+                                            Log.d(K9.LOG_TAG, "Got path '" + mSettings.getPathPrefix() + "' and separator '" + mSettings.getPathDelimeter() + "'");
                                     }
                                 }
                             }
@@ -2371,10 +2502,10 @@ public class ImapStore extends Store
                     {
                         if (K9.DEBUG)
                             Log.i(K9.LOG_TAG, "mPathPrefix is unset but server does not have NAMESPACE capability");
-                        mPathPrefix = "";
+                        mSettings.setPathPrefix("");
                     }
                 }
-                if (mPathDelimeter == null)
+                if (mSettings.getPathDelimeter() == null)
                 {
                     try
                     {
@@ -2384,10 +2515,10 @@ public class ImapStore extends Store
                         {
                             if (ImapResponseParser.equalsIgnoreCase(response.get(0), "LIST"))
                             {
-                                mPathDelimeter = response.getString(2);
-                                mCombinedPrefix = null;
+                                mSettings.setPathDelimeter(response.getString(2));
+                                mSettings.setCombinedPrefix(null);
                                 if (K9.DEBUG)
-                                    Log.d(K9.LOG_TAG, "Got path delimeter '" + mPathDelimeter + "' for " + getLogId());
+                                    Log.d(K9.LOG_TAG, "Got path delimeter '" + mSettings.getPathDelimeter() + "' for " + getLogId());
                             }
                         }
                     }
@@ -2463,7 +2594,7 @@ public class ImapStore extends Store
 
                 byte[] ipad = new byte[64];
                 byte[] opad = new byte[64];
-                byte[] secretBytes = mPassword.getBytes("US-ASCII");
+                byte[] secretBytes = mSettings.getPassword().getBytes("US-ASCII");
                 MessageDigest md = MessageDigest.getInstance("MD5");
                 if (secretBytes.length > 64)
                 {
@@ -2477,11 +2608,11 @@ public class ImapStore extends Store
                 byte[] firstPass = md.digest(nonce);
                 md.update(opad);
                 byte[] result = md.digest(firstPass);
-                String plainCRAM = mUsername + " " + new String(Hex.encodeHex(result));
+                String plainCRAM = mSettings.getUsername() + " " + new String(Hex.encodeHex(result));
                 byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes("US-ASCII"));
                 if (K9.DEBUG)
                 {
-                    Log.d(K9.LOG_TAG, "Username == " + mUsername);
+                    Log.d(K9.LOG_TAG, "Username == " + mSettings.getUsername());
                     Log.d(K9.LOG_TAG, "plainCRAM: " + plainCRAM);
                     Log.d(K9.LOG_TAG, "b64CRAM: " + new String(b64CRAM, "US-ASCII"));
                 }
@@ -2538,12 +2669,12 @@ public class ImapStore extends Store
             return capabilities.contains(capability.toUpperCase());
         }
 
-        private boolean isOpen()
+        public boolean isOpen()
         {
             return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket.isClosed());
         }
 
-        private void close()
+        public void close()
         {
 //            if (isOpen()) {
 //                try {
@@ -2581,12 +2712,12 @@ public class ImapStore extends Store
             mSocket = null;
         }
 
-        private ImapResponse readResponse() throws IOException, MessagingException
+        public ImapResponse readResponse() throws IOException, MessagingException
         {
             return readResponse(null);
         }
 
-        private ImapResponse readResponse(ImapResponseParser.IImapResponseCallback callback) throws IOException
+        public ImapResponse readResponse(ImapResponseParser.IImapResponseCallback callback) throws IOException
         {
             try
             {
@@ -2614,7 +2745,7 @@ public class ImapStore extends Store
             return out;
         }
 
-        private void sendContinuation(String continuation) throws IOException
+        public void sendContinuation(String continuation) throws IOException
         {
             mOut.write(continuation.getBytes());
             mOut.write('\r');
@@ -2683,7 +2814,7 @@ public class ImapStore extends Store
             return executeSimpleCommand(command, sensitive, null);
         }
 
-        private List executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler)
+        public List executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler)
         throws IOException, ImapException, MessagingException
         {
             String commandToLog = command;
diff --git a/src/com/fsck/k9/mail/transport/imap/ImapSettings.java b/src/com/fsck/k9/mail/transport/imap/ImapSettings.java
new file mode 100644
index 000000000..d16eb0273
--- /dev/null
+++ b/src/com/fsck/k9/mail/transport/imap/ImapSettings.java
@@ -0,0 +1,38 @@
+package com.fsck.k9.mail.transport.imap;
+
+import com.fsck.k9.mail.store.ImapStore;
+import com.fsck.k9.mail.store.ImapStore.AuthType;
+import com.fsck.k9.mail.store.ImapStore.ImapConnection;
+
+/**
+ * Settings source for IMAP. Implemented in order to remove coupling between {@link ImapStore} and {@link ImapConnection}.
+ */
+public interface ImapSettings
+{
+    String getHost();
+
+    int getPort();
+
+    int getConnectionSecurity();
+
+    AuthType getAuthType();
+
+    String getUsername();
+
+    String getPassword();
+
+    boolean useCompression(int type);
+
+    String getPathPrefix();
+
+    void setPathPrefix(String prefix);
+
+    String getPathDelimeter();
+
+    void setPathDelimeter(String delimeter);
+
+    String getCombinedPrefix();
+
+    void setCombinedPrefix(String prefix);
+
+}