diff --git a/src/com/fsck/k9/mail/ConnectionSecurity.java b/src/com/fsck/k9/mail/ConnectionSecurity.java new file mode 100644 index 000000000..bc6f2c8c2 --- /dev/null +++ b/src/com/fsck/k9/mail/ConnectionSecurity.java @@ -0,0 +1,18 @@ +package com.fsck.k9.mail; + +/** + * The currently available connection security types. + * + *
+ * Right now this enum is only used by {@link Store.StoreSettings} and converted to store-specific + * constants in the different Store implementations. In the future we probably want to change this + * and use {@code ConnectionSecurity} exclusively. + *
+ */ +public enum ConnectionSecurity { + NONE, + STARTTLS_OPTIONAL, + STARTTLS_REQUIRED, + SSL_TLS_OPTIONAL, + SSL_TLS_REQUIRED +} diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java index e112b0de7..11ed2d8ef 100644 --- a/src/com/fsck/k9/mail/Store.java +++ b/src/com/fsck/k9/mail/Store.java @@ -13,6 +13,7 @@ import com.fsck.k9.mail.store.StorageManager.StorageProvider; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Store is the access point for an email message store. It's location can be @@ -83,6 +84,117 @@ public abstract class Store { return (LocalStore) store; } + /** + * Decodes the contents of store-specific URIs and puts them into a {@link StoreSettings} + * object. + * + * @param uri + * the store-specific URI to decode + * + * @return A {@link StoreSettings} object holding the settings contained in the URI. + * + * @see ImapStore#decodeUri(String) + * @see Pop3Store#decodeUri(String) + * @see WebDavStore#decodeUri(String) + */ + public static StoreSettings decodeStoreUri(String uri) { + if (uri.startsWith("imap")) { + return ImapStore.decodeUri(uri); + } else if (uri.startsWith("pop3")) { + return Pop3Store.decodeUri(uri); + } else if (uri.startsWith("webdav")) { + return WebDavStore.decodeUri(uri); + } else { + throw new IllegalArgumentException("Not a valid store URI"); + } + } + + /** + * This is an abstraction to get rid of the store-specific URIs. + * + *+ * Right now it's only used for settings import/export. But the goal is to get rid of + * store URIs altogether. + *
+ * + * @see Account#getStoreUri() + */ + public static class StoreSettings { + /** + * The host name of the incoming server. + */ + public final String host; + + /** + * The port number of the incoming server. + */ + public final int port; + + /** + * The type of connection security to be used when connecting to the incoming server. + * + * {@link ConnectionSecurity#NONE} if not applicable for the store. + */ + public final ConnectionSecurity connectionSecurity; + + /** + * The authentication method to use when connecting to the incoming server. + * + * {@code null} if not applicable for the store. + */ + public final String authenticationType; + + /** + * The username part of the credentials needed to authenticate to the incoming server. + * + * {@code null} if unused or not applicable for the store. + */ + public final String username; + + /** + * The password part of the credentials needed to authenticate to the incoming server. + * + * {@code null} if unused or not applicable for the store. + */ + public final String password; + + + /** + * Creates a new {@code StoreSettings} object. + * + * @param host + * see {@link StoreSettings#host} + * @param port + * see {@link StoreSettings#port} + * @param connectionSecurity + * see {@link StoreSettings#connectionSecurity} + * @param authenticationType + * see {@link StoreSettings#authenticationType} + * @param username + * see {@link StoreSettings#username} + * @param password + * see {@link StoreSettings#password} + */ + public StoreSettings(String host, int port, ConnectionSecurity connectionSecurity, + String authenticationType, String username, String password) { + this.host = host; + this.port = port; + this.connectionSecurity = connectionSecurity; + this.authenticationType = authenticationType; + this.username = username; + this.password = password; + } + + /** + * Returns store-specific settings as key/value pair. + * + *Classes that inherit from this one are expected to override this method.
+ */ + public MapPossible forms:
+ *+ * imap://auth:user:password@server:port CONNECTION_SECURITY_NONE + * imap+tls://auth:user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL + * imap+tls+://auth:user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED + * imap+ssl+://auth:user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED + * imap+ssl://auth:user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL + *+ */ + public static ImapStoreSettings decodeUri(String uri) { + String host; + int port; + ConnectionSecurity connectionSecurity; + String authenticationType = null; + String username = null; + String password = null; + String pathPrefix = null; + + URI imapUri; + try { + imapUri = new URI(uri); + } catch (URISyntaxException use) { + throw new IllegalArgumentException("Invalid ImapStore URI", use); + } + + String scheme = imapUri.getScheme(); + if (scheme.equals("imap")) { + connectionSecurity = ConnectionSecurity.NONE; + port = 143; + } else if (scheme.equals("imap+tls")) { + connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; + port = 143; + } else if (scheme.equals("imap+tls+")) { + connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; + port = 143; + } else if (scheme.equals("imap+ssl+")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; + port = 993; + } else if (scheme.equals("imap+ssl")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; + port = 993; + } else { + throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); + } + + host = imapUri.getHost(); + + if (imapUri.getPort() != -1) { + port = imapUri.getPort(); + } + + if (imapUri.getUserInfo() != null) { + try { + String[] userInfoParts = imapUri.getUserInfo().split(":"); + if (userInfoParts.length == 2) { + authenticationType = AuthType.PLAIN.name(); + username = URLDecoder.decode(userInfoParts[0], "UTF-8"); + password = URLDecoder.decode(userInfoParts[1], "UTF-8"); + } else { + authenticationType = AuthType.valueOf(userInfoParts[0]).name(); + username = URLDecoder.decode(userInfoParts[1], "UTF-8"); + password = URLDecoder.decode(userInfoParts[2], "UTF-8"); + } + } catch (UnsupportedEncodingException enc) { + // This shouldn't happen since the encoding is hardcoded to UTF-8 + throw new IllegalArgumentException("Couldn't urldecode username or password.", enc); + } + } + + String path = imapUri.getPath(); + if (path != null && path.length() > 0) { + pathPrefix = path.substring(1); + if (pathPrefix != null && pathPrefix.trim().length() == 0) { + pathPrefix = null; + } + } + + return new ImapStoreSettings(host, port, connectionSecurity, authenticationType, username, + password, pathPrefix); + } + + /** + * This class is used to store the decoded contents of an ImapStore URI. + * + * @see ImapStore#decodeUri(String) + */ + private static class ImapStoreSettings extends StoreSettings { + private static final String PATH_PREFIX_KEY = "path_prefix"; + + public final String pathPrefix; + + protected ImapStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, + String authenticationType, String username, String password, String pathPrefix) { + super(host, port, connectionSecurity, authenticationType, username, password); + this.pathPrefix = pathPrefix; + } + + @Override + public Map
Possible forms:
+ *+ * pop3://user:password@server:port CONNECTION_SECURITY_NONE + * pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL + * pop3+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED + * pop3+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED + * pop3+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL + *+ */ + public static StoreSettings decodeUri(String uri) { + String host; + int port; + ConnectionSecurity connectionSecurity; + String username = null; + String password = null; + + URI pop3Uri; + try { + pop3Uri = new URI(uri); + } catch (URISyntaxException use) { + throw new IllegalArgumentException("Invalid Pop3Store URI", use); + } + + String scheme = pop3Uri.getScheme(); + if (scheme.equals("pop3")) { + connectionSecurity = ConnectionSecurity.NONE; + port = 110; + } else if (scheme.equals("pop3+tls")) { + connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; + port = 110; + } else if (scheme.equals("pop3+tls+")) { + connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; + port = 110; + } else if (scheme.equals("pop3+ssl+")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; + port = 995; + } else if (scheme.equals("pop3+ssl")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; + port = 995; + } else { + throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); + } + + host = pop3Uri.getHost(); + + if (pop3Uri.getPort() != -1) { + port = pop3Uri.getPort(); + } + + if (pop3Uri.getUserInfo() != null) { + try { + String[] userInfoParts = pop3Uri.getUserInfo().split(":"); + username = URLDecoder.decode(userInfoParts[0], "UTF-8"); + if (userInfoParts.length > 1) { + password = URLDecoder.decode(userInfoParts[1], "UTF-8"); + } + } catch (UnsupportedEncodingException enc) { + // This shouldn't happen since the encoding is hardcoded to UTF-8 + throw new IllegalArgumentException("Couldn't urldecode username or password.", enc); + } + } + + return new StoreSettings(host, port, connectionSecurity, null, username, password); + } + + private String mHost; private int mPort; private String mUsername; @@ -42,61 +111,40 @@ public class Pop3Store extends Store { private HashMap
Possible forms:
+ *+ * webdav://user:password@server:port CONNECTION_SECURITY_NONE + * webdav+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL + * webdav+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED + * webdav+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED + * webdav+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL + *+ */ + public static WebDavStoreSettings decodeUri(String uri) { + String host; + int port; + ConnectionSecurity connectionSecurity; + String username = null; + String password = null; + String alias = null; + String path = null; + String authPath = null; + String mailboxPath = null; + + + URI webDavUri; + try { + webDavUri = new URI(uri); + } catch (URISyntaxException use) { + throw new IllegalArgumentException("Invalid WebDavStore URI", use); + } + + String scheme = webDavUri.getScheme(); + if (scheme.equals("webdav")) { + connectionSecurity = ConnectionSecurity.NONE; + } else if (scheme.equals("webdav+ssl")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; + } else if (scheme.equals("webdav+ssl+")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; + } else if (scheme.equals("webdav+tls")) { + connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; + } else if (scheme.equals("webdav+tls+")) { + connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; + } else { + throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); + } + + host = webDavUri.getHost(); + if (host.startsWith("http")) { + String[] hostParts = host.split("://", 2); + if (hostParts.length > 1) { + host = hostParts[1]; + } + } + + port = webDavUri.getPort(); + + String userInfo = webDavUri.getUserInfo(); + if (userInfo != null) { + try { + String[] userInfoParts = userInfo.split(":"); + username = URLDecoder.decode(userInfoParts[0], "UTF-8"); + String userParts[] = username.split("\\\\", 2); + + if (userParts.length > 1) { + alias = userParts[1]; + } else { + alias = username; + } + if (userInfoParts.length > 1) { + password = URLDecoder.decode(userInfoParts[1], "UTF-8"); + } + } catch (UnsupportedEncodingException enc) { + // This shouldn't happen since the encoding is hardcoded to UTF-8 + throw new IllegalArgumentException("Couldn't urldecode username or password.", enc); + } + } + + String[] pathParts = webDavUri.getPath().split("\\|"); + for (int i = 0, count = pathParts.length; i < count; i++) { + if (i == 0) { + if (pathParts[0] != null && + pathParts[0].length() > 1) { + path = pathParts[0]; + } + } else if (i == 1) { + if (pathParts[1] != null && + pathParts[1].length() > 1) { + authPath = pathParts[1]; + } + } else if (i == 2) { + if (pathParts[2] != null && + pathParts[2].length() > 1) { + mailboxPath = pathParts[2]; + } + } + } + + return new WebDavStoreSettings(host, port, connectionSecurity, null, + username, password, alias, path, authPath, mailboxPath, webDavUri); + } + + /** + * This class is used to store the decoded contents of an WebDavStore URI. + * + * @see WebDavStore#decodeUri(String) + */ + private static class WebDavStoreSettings extends StoreSettings { + private static final String ALIAS_KEY = "alias"; + private static final String PATH_KEY = "path"; + private static final String AUTH_PATH_KEY = "authPath"; + private static final String MAILBOX_PATH_KEY = "mailboxPath"; + + public final String alias; + public final String path; + public final String authPath; + public final String mailboxPath; + public final URI uri; + + protected WebDavStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, + String authenticationType, String username, String password, String alias, + String path, String authPath, String mailboxPath, URI uri) { + super(host, port, connectionSecurity, authenticationType, username, password); + this.alias = alias; + this.path = path; + this.authPath = authPath; + this.mailboxPath = mailboxPath; + this.uri = uri; + } + + @Override + public Map