diff --git a/res/values/strings.xml b/res/values/strings.xml
index f6487c67f..00f683d7b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -350,6 +350,8 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
Retrieving account information\u2026
Checking incoming server settings\u2026
Checking outgoing server settings\u2026
+ Authenticating\u2026
+ Fetching account settings\u2026
Finishing\u2026
Canceling\u2026
@@ -364,7 +366,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
What type of account is this?
POP3
IMAP
- WebDAV (Exchange)
+ Exchange (WebDAV)
Incoming server settings
Username
diff --git a/src/com/fsck/k9/activity/MessageView.java b/src/com/fsck/k9/activity/MessageView.java
index c8fc78335..1c59ac6e5 100644
--- a/src/com/fsck/k9/activity/MessageView.java
+++ b/src/com/fsck/k9/activity/MessageView.java
@@ -889,7 +889,9 @@ public class MessageView extends K9Activity implements OnClickListener
if (K9.mobileOptimizedLayout())
{
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
- } else {
+ }
+ else
+ {
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
}
diff --git a/src/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java b/src/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java
index 8e8408de8..7f55fd899 100644
--- a/src/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java
+++ b/src/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java
@@ -22,6 +22,8 @@ import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.TrustManagerFactory;
+import com.fsck.k9.mail.store.WebDavStore;
+
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@@ -106,13 +108,24 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
}
if (mCheckIncoming)
{
- setMessage(R.string.account_setup_check_settings_check_incoming_msg);
store = mAccount.getRemoteStore();
+
+ if (store instanceof WebDavStore)
+ {
+ setMessage(R.string.account_setup_check_settings_authenticate);
+ }
+ else
+ {
+ setMessage(R.string.account_setup_check_settings_check_incoming_msg);
+ }
store.checkSettings();
- MessagingController.getInstance(getApplication()).listFolders(mAccount, true, null);
+ if (store instanceof WebDavStore)
+ {
+ setMessage(R.string.account_setup_check_settings_fetch);
+ }
+ MessagingController.getInstance(getApplication()).listFoldersSynchronous(mAccount, true, null);
MessagingController.getInstance(getApplication()).synchronizeMailbox(mAccount, K9.INBOX , null, null);
-
}
if (mDestroyed)
{
@@ -125,7 +138,10 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
}
if (mCheckOutgoing)
{
- setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
+ if (!(mAccount.getRemoteStore() instanceof WebDavStore))
+ {
+ setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
+ }
Transport transport = Transport.getInstance(mAccount);
transport.close();
transport.open();
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index c1799a882..8912f5776 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -409,61 +409,79 @@ public class MessagingController implements Runnable
{
public void run()
{
- for (MessagingListener l : getListeners(listener))
- {
- l.listFoldersStarted(account);
- }
- List extends Folder> localFolders = null;
- try
- {
- Store localStore = account.getLocalStore();
- localFolders = localStore.getPersonalNamespaces(false);
-
- Folder[] folderArray = localFolders.toArray(EMPTY_FOLDER_ARRAY);
-
- if (refreshRemote || localFolders == null || localFolders.size() == 0)
- {
- doRefreshRemote(account, listener);
- return;
- }
-
- for (MessagingListener l : getListeners(listener))
- {
- l.listFolders(account, folderArray);
- }
- }
- catch (Exception e)
- {
- for (MessagingListener l : getListeners(listener))
- {
- l.listFoldersFailed(account, e.getMessage());
- }
-
- addErrorMessage(account, null, e);
- return;
- }
- finally
- {
- if (localFolders != null)
- {
- for (Folder localFolder : localFolders)
- {
- if (localFolder != null)
- {
- localFolder.close();
- }
- }
- }
- }
-
- for (MessagingListener l : getListeners(listener))
- {
- l.listFoldersFinished(account);
- }
+ listFoldersSynchronous(account, refreshRemote, listener);
}
});
}
+ /**
+ * Lists folders that are available locally and remotely. This method calls
+ * listFoldersCallback for local folders before it returns, and then for
+ * remote folders at some later point. If there are no local folders
+ * includeRemote is forced by this method. This method is called in the
+ * foreground.
+ * TODO this needs to cache the remote folder list
+ *
+ * @param account
+ * @param includeRemote
+ * @param listener
+ * @throws MessagingException
+ */
+ public void listFoldersSynchronous(final Account account, final boolean refreshRemote, final MessagingListener listener)
+ {
+ for (MessagingListener l : getListeners(listener))
+ {
+ l.listFoldersStarted(account);
+ }
+ List extends Folder> localFolders = null;
+ try
+ {
+ Store localStore = account.getLocalStore();
+ localFolders = localStore.getPersonalNamespaces(false);
+
+ Folder[] folderArray = localFolders.toArray(EMPTY_FOLDER_ARRAY);
+
+ if (refreshRemote || localFolders == null || localFolders.size() == 0)
+ {
+ doRefreshRemote(account, listener);
+ return;
+ }
+
+ for (MessagingListener l : getListeners(listener))
+ {
+ l.listFolders(account, folderArray);
+ }
+ }
+ catch (Exception e)
+ {
+ for (MessagingListener l : getListeners(listener))
+ {
+ l.listFoldersFailed(account, e.getMessage());
+ }
+
+ addErrorMessage(account, null, e);
+ return;
+ }
+ finally
+ {
+ if (localFolders != null)
+ {
+ for (Folder localFolder : localFolders)
+ {
+ if (localFolder != null)
+ {
+ localFolder.close();
+ }
+ }
+ }
+ }
+
+ for (MessagingListener l : getListeners(listener))
+ {
+ l.listFoldersFinished(account);
+ }
+ }
+
private void doRefreshRemote(final Account account, MessagingListener listener)
{
put("doRefreshRemote", listener, new Runnable()
diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java
index 2ecb9e794..88bbe2d46 100644
--- a/src/com/fsck/k9/mail/store/WebDavStore.java
+++ b/src/com/fsck/k9/mail/store/WebDavStore.java
@@ -12,20 +12,19 @@ import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.transport.TrustedSocketFactory;
import org.apache.http.*;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.Credentials;
-import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CookieStore;
-import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.xml.sax.Attributes;
@@ -34,7 +33,6 @@ import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
-import javax.net.ssl.SSLException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -65,11 +63,17 @@ import java.util.zip.GZIPInputStream;
*/
public class WebDavStore extends Store
{
- public static final int CONNECTION_SECURITY_NONE = 0;
- public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
- public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
- public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
- public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
+ // Security options
+ private static final short CONNECTION_SECURITY_NONE = 0;
+ private static final short CONNECTION_SECURITY_TLS_OPTIONAL = 1;
+ private static final short CONNECTION_SECURITY_TLS_REQUIRED = 2;
+ private static final short CONNECTION_SECURITY_SSL_OPTIONAL = 3;
+ private static final short CONNECTION_SECURITY_SSL_REQUIRED = 4;
+
+ // Authentication types
+ private static final short AUTH_TYPE_NONE = 0;
+ private static final short AUTH_TYPE_BASIC = 1;
+ private static final short AUTH_TYPE_FORM_BASED = 2;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN, Flag.ANSWERED };
@@ -77,9 +81,12 @@ public class WebDavStore extends Store
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
- private int mConnectionSecurity;
+ private static final String DAV_MAIL_SEND_FOLDER = "##DavMailSubmissionURI##";
+ private static final String DAV_MAIL_TMP_FOLDER = "drafts";
+
+ private short mConnectionSecurity;
private String mUsername; /* Stores the username for authentications */
- private String alias;
+ private String mAlias; /* Stores the alias for the user's mailbox */
private String mPassword; /* Stores the password for authentications */
private String mUrl; /* Stores the base URL for the server */
private String mHost; /* Stores the host name for the server */
@@ -89,18 +96,16 @@ public class WebDavStore extends Store
private URI mUri; /* Stores the Uniform Resource Indicator with all connection info */
private String mRedirectUrl;
private String mAuthString;
- private static String DAV_MAIL_SEND_FOLDER = "##DavMailSubmissionURI##";
- private static String DAV_MAIL_TMP_FOLDER = "drafts";
-
- private CookieStore mAuthCookies; /* Stores cookies from authentication */
- private boolean mAuthenticated = false; /* Stores authentication state */
- private long mLastAuth = -1; /* Stores the timestamp of last auth */
+ private boolean mSecure;
+ private WebDavHttpClient mHttpClient = null;
+ private HttpContext mContext = null;
+ private CookieStore mAuthCookies = null;
+ private short mAuthentication = AUTH_TYPE_NONE;
+ private long mLastAuth = -1;
private long mAuthTimeout = 5 * 60;
private HashMap mFolderList = new HashMap();
- private boolean mSecure;
- private WebDavHttpClient mHttpClient = null;
/**
* webdav://user:password@server:port CONNECTION_SECURITY_NONE
@@ -121,6 +126,7 @@ public class WebDavStore extends Store
{
throw new MessagingException("Invalid WebDavStore URI", use);
}
+
String scheme = mUri.getScheme();
if (scheme.equals("webdav"))
{
@@ -157,6 +163,34 @@ public class WebDavStore extends Store
}
}
+ if (mUri.getUserInfo() != null)
+ {
+ try
+ {
+ String[] userInfoParts = mUri.getUserInfo().split(":");
+ mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8");
+ String userParts[] = mUsername.split("\\\\", 2);
+
+ if (userParts.length > 1)
+ {
+ mAlias = userParts[1];
+ }
+ else
+ {
+ mAlias = mUsername;
+ }
+ if (userInfoParts.length > 1)
+ {
+ mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8");
+ }
+ }
+ catch (UnsupportedEncodingException enc)
+ {
+ // This shouldn't happen since the encoding is hardcoded to UTF-8
+ Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc);
+ }
+ }
+
String[] pathParts = mUri.getPath().split("\\|");
for (int i = 0, count = pathParts.length; i < count; i++)
@@ -202,43 +236,27 @@ public class WebDavStore extends Store
}
}
}
- String path = mPath;
- if (path.length() > 0 && !path.startsWith("/"))
+
+ if (!this.mPath.equals("") &&
+ this.mPath.startsWith("/"))
{
- path = "/" + mPath;
+ mPath = "/" + mPath;
}
- this.mUrl = getRoot() + path;
-
- if (mUri.getUserInfo() != null)
+ if (this.mMailboxPath == null ||
+ this.mMailboxPath.equals(""))
{
- try
- {
- String[] userInfoParts = mUri.getUserInfo().split(":");
- mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8");
- String userParts[] = mUsername.split("/", 2);
-
- if (userParts.length > 1)
- {
- alias = userParts[1];
- }
- else
- {
- alias = mUsername;
- }
- if (userInfoParts.length > 1)
- {
- mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8");
- }
- }
- catch (UnsupportedEncodingException enc)
- {
- // This shouldn't happen since the encoding is hardcoded to UTF-8
- Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc);
- }
+ this.mMailboxPath = "/Exchange/" + this.mAlias;
}
- mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
- mAuthString = "Basic " + Utility.base64Encode(mUsername + ":" + mPassword);
+ else if (!this.mMailboxPath.startsWith("/"))
+ {
+ mMailboxPath = "/" + mMailboxPath;
+ }
+
+ this.mUrl = getRoot() + mPath + mMailboxPath;
+
+ this.mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
+ this.mAuthString = "Basic " + Utility.base64Encode(mUsername + ":" + mPassword);
}
private String getRoot()
@@ -259,11 +277,10 @@ public class WebDavStore extends Store
return root;
}
-
@Override
public void checkSettings() throws MessagingException
{
- Log.e(K9.LOG_TAG, "WebDavStore.checkSettings() not implemented");
+ authenticate();
}
@Override
@@ -531,16 +548,58 @@ public class WebDavStore extends Store
*/
/**
- * Performs Form Based authentication regardless of the current
- * authentication state
+ * Determines which type of authentication Exchange is using and
+ * authenticates appropriately.
* @throws MessagingException
*/
- public void authenticate() throws MessagingException
+ public boolean authenticate() throws MessagingException
{
try
{
- doFBA();
- //this.mAuthCookies = doAuthentication(this.mUsername, this.mPassword, this.mUrl);
+ if (mAuthentication == AUTH_TYPE_NONE)
+ {
+ ConnectionInfo info = doInitialConnection();
+
+ if (info.requiredAuthType == AUTH_TYPE_BASIC)
+ {
+ HttpGeneric request = new HttpGeneric(mUrl);
+ request.setMethod("GET");
+ request.setHeader("Authorization", mAuthString);
+
+ WebDavHttpClient httpClient = new WebDavHttpClient();
+ HttpResponse response = httpClient.executeOverride(request, mContext);
+
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300)
+ {
+ mAuthentication = AUTH_TYPE_BASIC;
+ mLastAuth = System.currentTimeMillis() / 1000;
+ }
+ else if (statusCode == 401)
+ {
+ throw new MessagingException("Invalid username or password for authentication.");
+ }
+ else
+ {
+ throw new MessagingException("Error with code " + response.getStatusLine().getStatusCode() +
+ " during request processing: " + response.getStatusLine().toString());
+ }
+ }
+ else if (info.requiredAuthType == AUTH_TYPE_FORM_BASED)
+ {
+ doFBA(info);
+ }
+ }
+ else if (mAuthentication == AUTH_TYPE_BASIC)
+ {
+ // Nothing to do, we authenticate with every request when
+ // using basic authentication.
+ }
+ else if (mAuthentication == AUTH_TYPE_FORM_BASED)
+ {
+ // Our cookie expired, re-authenticate.
+ doFBA(null);
+ }
}
catch (IOException ioe)
{
@@ -548,143 +607,154 @@ public class WebDavStore extends Store
throw new MessagingException("Error during authentication", ioe);
}
- if (this.mAuthCookies == null)
- {
- this.mAuthenticated = false;
- }
- else
- {
- this.mAuthenticated = true;
- this.mLastAuth = System.currentTimeMillis()/1000;
- }
+ return mAuthentication != AUTH_TYPE_NONE;
}
/**
- * Determines if a new authentication is needed.
- * Returns true if new authentication is needed.
- */
- public boolean needAuth()
- {
- boolean status = false;
- long currentTime = -1;
- if (!this.mAuthenticated)
- {
- status = true;
- }
-
- currentTime = System.currentTimeMillis()/1000;
- if ((currentTime - this.mLastAuth) > (this.mAuthTimeout))
- {
- status = true;
- }
- return status;
- }
-
- public static String getHttpRequestResponse(HttpEntity request, HttpEntity response) throws IllegalStateException, IOException
- {
- String responseText = "";
- String requestText = "";
- if (response != null)
- {
- BufferedReader reader = new BufferedReader(new InputStreamReader(WebDavHttpClient.getUngzippedContent(response)), 8192);
- String tempText = "";
-
- while ((tempText = reader.readLine()) != null)
- {
- responseText += tempText;
- }
- }
- if (request != null)
- {
- BufferedReader reader = new BufferedReader(new InputStreamReader(WebDavHttpClient.getUngzippedContent(response)), 8192);
- String tempText = "";
-
- while ((tempText = reader.readLine()) != null)
- {
- requestText += tempText;
- }
- requestText = requestText.replaceAll("password=.*?&", "password=(omitted)&");
- }
- return "Request: " + requestText +
- "\n\nResponse: " + responseText;
-
- }
-
- /**
- * Performs the Form Based Authentication
- * Returns the CookieStore object for later use or null
+ * Makes the initial connection to Exchange for authentication.
+ * Determines the type of authentication necessary for the server.
* @throws MessagingException
*/
- public void doFBA() throws IOException, MessagingException
+ private ConnectionInfo doInitialConnection() throws MessagingException
{
- /* public CookieStore doAuthentication(String username, String password,
- String url) throws IOException, MessagingException {*/
- String authPath;
- String url = this.mUrl;
- String username = this.mUsername;
- String password = this.mPassword;
- String[] urlParts = url.split("/");
- String finalUrl = "";
- String loginUrl = "";
- String destinationUrl = "";
+ // For our initial connection we are sending an empty GET request to
+ // the configured URL, which should be in the following form:
+ // https://mail.server.com/Exchange/alias
+ //
+ // Possible status codes include:
+ // 401 - the server uses basic authentication
+ // 30x - the server is trying to redirect us to an OWA login
+ // 20x - success
+ //
+ // The latter two indicate form-based authentication.
+ ConnectionInfo info = new ConnectionInfo();
- if (this.mAuthPath != null &&
- !this.mAuthPath.equals("") &&
- !this.mAuthPath.equals("/"))
- {
- authPath = this.mAuthPath;
- }
- else
- {
- authPath = "/exchweb/bin/auth/owaauth.dll";
- }
+ WebDavHttpClient httpClient = getHttpClient();
- for (int i = 0; i <= 2; i++)
- {
- if (i != 0)
- {
- finalUrl = finalUrl + "/" + urlParts[i];
- }
- else
- {
- finalUrl = urlParts[i];
- }
- }
-
- if (finalUrl.equals(""))
- {
- throw new MessagingException("doFBA failed, unable to construct URL to post login credentials to.");
- }
-
- loginUrl = finalUrl + authPath;
+ HttpGeneric request = new HttpGeneric(mUrl);
+ request.setMethod("GET");
try
{
- /* Browser Client */
- WebDavHttpClient httpclient = mHttpClient;
+ HttpResponse response = httpClient.executeOverride(request, mContext);
+ info.statusCode = response.getStatusLine().getStatusCode();
- /**
- * This is in a separate block because I really don't like how it's done.
- * This basically scrapes the OWA login page for the form submission URL.
- * UGLY!WebDavHttpClient
- * Added an if-check to see if there's a user supplied authentication path for FBA
- */
- if (this.mAuthPath == null ||
- this.mAuthPath.equals("") ||
- this.mAuthPath.equals("/"))
+ if (info.statusCode == 401)
{
+ // 401 is the "Unauthorized" status code, meaning the server wants
+ // an authentication header for basic authentication.
+ info.requiredAuthType = AUTH_TYPE_BASIC;
+ }
+ else if ((info.statusCode >= 200 && info.statusCode < 300) || // Success
+ (info.statusCode >= 300 && info.statusCode < 400) || // Redirect
+ (info.statusCode == 440)) // Unauthorized
- httpclient.addRequestInterceptor(new HttpRequestInterceptor()
+ {
+ // We will handle all 3 situations the same. First we take an educated
+ // guess at where the authorization DLL is located. If this is this
+ // doesn't work, then we'll use the redirection URL for OWA login given
+ // to use by exchange. We can use this to scrape the location of the
+ // authorization URL.
+ info.requiredAuthType = AUTH_TYPE_FORM_BASED;
+
+ if (mAuthPath != null && !mAuthPath.equals(""))
{
- public void process(HttpRequest request, HttpContext context)
- throws HttpException, IOException
- {
- mRedirectUrl = ((HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST)).toURI() + request.getRequestLine().getUri();
- }
- });
- HashMap headers = new HashMap();
- InputStream istream = sendRequest(finalUrl, "GET", null, headers, false);
+ // The user specified their own authentication path, use that.
+ info.guessedAuthUrl = getRoot() + mAuthPath;
+ }
+ else
+ {
+ // Use the default path to the authentication dll.
+ info.guessedAuthUrl = getRoot() + "/exchweb/bin/auth/owaauth.dll";
+ }
+ // Determine where the server is trying to redirect us.
+ Header location = response.getFirstHeader("Location");
+ if (location != null)
+ {
+ info.redirectUrl = location.getValue();
+ }
+ }
+ else
+ {
+ throw new IOException("Error with code " + info.statusCode + " during request processing: "+
+ response.getStatusLine().toString());
+ }
+ }
+ catch(IOException ioe)
+ {
+ Log.e(K9.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
+ throw new MessagingException("IOException", ioe);
+ }
+
+ return info;
+ }
+
+ /**
+ * Performs form-based authentication.
+ * @throws MessagingException
+ */
+ public void doFBA(ConnectionInfo info) throws IOException, MessagingException
+ {
+ WebDavHttpClient httpClient = getHttpClient();
+
+ HttpGeneric request = new HttpGeneric(info.guessedAuthUrl);
+ request.setMethod("POST");
+
+ // Build the POST data.
+ ArrayList pairs = new ArrayList();
+ pairs.add(new BasicNameValuePair("destination", mUrl));
+ pairs.add(new BasicNameValuePair("username", mUsername));
+ pairs.add(new BasicNameValuePair("password", mPassword));
+ pairs.add(new BasicNameValuePair("flags", "0"));
+ pairs.add(new BasicNameValuePair("SubmitCreds", "Log+On"));
+ pairs.add(new BasicNameValuePair("forcedownlevel", "0"));
+ pairs.add(new BasicNameValuePair("trusted", "0"));
+
+ UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(pairs);
+ request.setEntity(formEntity);
+
+ HttpResponse response = httpClient.executeOverride(request, mContext);
+ int statusCode = response.getStatusLine().getStatusCode();
+
+ if (statusCode >= 200 && statusCode < 300)
+ {
+ // Success, we're logged on and an authentication cookie should
+ // have already been added to mAuthCookies for us.
+ }
+ else if (statusCode == 404)
+ {
+ // The resource was not found, which means we need to get tricky
+ // about finding the correct login path.
+ // Send a request to our original redirect URL, and scrape the
+ // login path from the returned page.
+ httpClient.addRequestInterceptor(new HttpRequestInterceptor()
+ {
+ public void process(HttpRequest request, HttpContext context)
+ throws HttpException, IOException
+ {
+ mRedirectUrl = ((HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST)).toURI() +
+ request.getRequestLine().getUri();
+ }
+ });
+
+ String loginUrl = "";
+ if (info != null)
+ {
+ loginUrl = info.redirectUrl;
+ }
+ else if (mRedirectUrl != null && !mRedirectUrl.equals(""))
+ {
+ loginUrl = mRedirectUrl;
+ }
+ else
+ {
+ throw new MessagingException("No valid login URL available for form-based authentication.");
+ }
+
+ try
+ {
+ InputStream istream = sendRequest(loginUrl, "GET", null, null, false);
if (istream != null)
{
BufferedReader reader = new BufferedReader(new InputStreamReader(istream), 4096);
@@ -705,104 +775,36 @@ public class WebDavStore extends Store
mRedirectUrl = mRedirectUrl.substring(0, mRedirectUrl.lastIndexOf('?'));
mRedirectUrl = mRedirectUrl.substring(0, mRedirectUrl.lastIndexOf('/'));
loginUrl = mRedirectUrl + "/" + tagParts[1];
- this.mAuthPath = "/" + tagParts[1];
+ this.mAuthPath = new URI(loginUrl).getPath();
}
else
{
- loginUrl = finalUrl + tagParts[1];
- this.mAuthPath = "/" + tagParts[1];
- }
- }
-
- if (tempText.indexOf("destination") >= 0)
- {
- String[] tagParts = tempText.split("value");
- if (tagParts[1] != null)
- {
- String[] valueParts = tagParts[1].split("\"");
- destinationUrl = valueParts[1];
- matched = true;
+ loginUrl = getRoot() + tagParts[1];
+ this.mAuthPath = new URI(loginUrl).getPath();
}
}
}
istream.close();
+
+ // Now retry the login using our scraped login URL.
+ request = new HttpGeneric(loginUrl);
+ request.setMethod("POST");
+ request.setEntity(formEntity);
+
+ httpClient.executeOverride(request, mContext);
}
}
-
-
- /** Build the POST data to use */
- ArrayList pairs = new ArrayList();
- pairs.add(new BasicNameValuePair("username", username));
- pairs.add(new BasicNameValuePair("password", password));
- if (this.mMailboxPath != null &&
- !this.mMailboxPath.equals(""))
+ catch(URISyntaxException use)
{
- pairs.add(new BasicNameValuePair("destination", finalUrl + this.mMailboxPath));
- }
- else if (destinationUrl != null &&
- !destinationUrl.equals(""))
- {
- pairs.add(new BasicNameValuePair("destination", destinationUrl));
- }
- else
- {
- pairs.add(new BasicNameValuePair("destination", "/"));
- }
- pairs.add(new BasicNameValuePair("flags", "0"));
- pairs.add(new BasicNameValuePair("SubmitCreds", "Log+On"));
- pairs.add(new BasicNameValuePair("forcedownlevel", "0"));
- pairs.add(new BasicNameValuePair("trusted", "0"));
-
- try
- {
- UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(pairs);
- HashMap headers = new HashMap();
- String tempUrl = "";
- InputStream istream = sendRequest(loginUrl, "POST", formEntity, headers, false);
-
- /** Get the URL for the mailbox and set it for the store */
- if (istream != null)
- {
- BufferedReader reader = new BufferedReader(new InputStreamReader(istream), 8192);
- String tempText = "";
-
- while ((tempText = reader.readLine()) != null)
- {
- if (tempText.indexOf("BASE href") >= 0)
- {
- String[] tagParts = tempText.split("\"");
- tempUrl = tagParts[1];
- }
- }
- }
-
- if (this.mMailboxPath != null &&
- !this.mMailboxPath.equals(""))
- {
- this.mUrl = finalUrl + "/" + this.mMailboxPath + "/";
- }
- else if (tempUrl.equals(""))
- {
- this.mUrl = finalUrl + "/Exchange/" + this.alias + "/";
- }
- else
- {
- this.mUrl = tempUrl;
- }
-
- }
- catch (UnsupportedEncodingException uee)
- {
- Log.e(K9.LOG_TAG, "Error encoding POST data for authentication: " + uee + "\nTrace: " + processException(uee));
- throw new MessagingException("Error encoding POST data for authentication", uee);
+ throw new MessagingException("An invalid login URL was detected: " + loginUrl);
}
}
- catch (SSLException e)
+
+ if (mAuthCookies != null && !mAuthCookies.getCookies().isEmpty())
{
- throw new CertificateValidationException(e.getMessage(), e);
+ mAuthentication = AUTH_TYPE_FORM_BASED;
+ mLastAuth = System.currentTimeMillis() / 1000;
}
-
- this.mAuthenticated = true;
}
public CookieStore getAuthCookies()
@@ -812,7 +814,7 @@ public class WebDavStore extends Store
public String getAlias()
{
- return alias;
+ return mAlias;
}
public String getUrl()
@@ -822,100 +824,38 @@ public class WebDavStore extends Store
public WebDavHttpClient getHttpClient() throws MessagingException
{
- SchemeRegistry reg;
- Scheme s;
- boolean needAuth = false;
-
if (mHttpClient == null)
{
mHttpClient = new WebDavHttpClient();
- needAuth = true;
- }
- reg = mHttpClient.getConnectionManager().getSchemeRegistry();
- try
- {
- // Log.i(K9.LOG_TAG, "getHttpClient mHost = " + mHost);
- s = new Scheme("https", new TrustedSocketFactory(mHost, mSecure), 443);
- }
- catch (NoSuchAlgorithmException nsa)
- {
- Log.e(K9.LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa);
- throw new MessagingException("NoSuchAlgorithmException in getHttpClient: " + nsa);
- }
- catch (KeyManagementException kme)
- {
- Log.e(K9.LOG_TAG, "KeyManagementException in getHttpClient: " + kme);
- throw new MessagingException("KeyManagementException in getHttpClient: " + kme);
- }
- reg.register(s);
+ // Setup a cookie store for forms-based authentication.
+ mContext = new BasicHttpContext();
+ mAuthCookies = new BasicCookieStore();
+ mContext.setAttribute(ClientContext.COOKIE_STORE, mAuthCookies);
- if (needAuth)
- {
- HashMap headers = new HashMap();
- processRequest(this.mUrl, "GET", null, headers, false);
- }
-
- /*
- if (needAuth()) {
- if (!checkAuth()) {
- try {
- CookieStore cookies = mHttpClient.getCookieStore();
- cookies.clear();
- mHttpClient.setCookieStore(cookies);
- cookies = doAuthentication(this.mUsername, this.mPassword, this.mUrl);
- if (cookies != null) {
- this.mAuthenticated = true;
- this.mLastAuth = System.currentTimeMillis()/1000;
- }
- mHttpClient.setCookieStore(cookies);
- } catch (IOException ioe) {
- Log.e(K9.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
- }
- } else {
- Credentials creds = new UsernamePasswordCredentials(mUsername, mPassword);
- CredentialsProvider credsProvider = mHttpClient.getCredentialsProvider();
- credsProvider.setCredentials(new AuthScope(mHost, 80, AuthScope.ANY_REALM), creds);
- credsProvider.setCredentials(new AuthScope(mHost, 443, AuthScope.ANY_REALM), creds);
- credsProvider.setCredentials(new AuthScope(mHost, mUri.getPort(), AuthScope.ANY_REALM), creds);
- mHttpClient.setCredentialsProvider(credsProvider);
- // Assume we're authenticated and ok here since the checkAuth() was 401 and we've now set the credentials
- this.mAuthenticated = true;
- this.mLastAuth = System.currentTimeMillis()/1000;
+ SchemeRegistry reg = mHttpClient.getConnectionManager().getSchemeRegistry();
+ try
+ {
+ Scheme s = new Scheme("https", new TrustedSocketFactory(mHost, mSecure), 443);
+ reg.register(s);
+ }
+ catch (NoSuchAlgorithmException nsa)
+ {
+ Log.e(K9.LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa);
+ throw new MessagingException("NoSuchAlgorithmException in getHttpClient: " + nsa);
+ }
+ catch (KeyManagementException kme)
+ {
+ Log.e(K9.LOG_TAG, "KeyManagementException in getHttpClient: " + kme);
+ throw new MessagingException("KeyManagementException in getHttpClient: " + kme);
}
}
- */
- return mHttpClient;
- }
-
- public WebDavHttpClient getTrustedHttpClient() throws KeyManagementException, NoSuchAlgorithmException
- {
- if (mHttpClient == null)
- {
- mHttpClient = new WebDavHttpClient();
- SchemeRegistry reg = mHttpClient.getConnectionManager().getSchemeRegistry();
- Scheme s = new Scheme("https",new TrustedSocketFactory(mHost,mSecure),443);
- reg.register(s);
-
-
- //Add credentials for NTLM/Digest/Basic Auth
- Credentials creds = new UsernamePasswordCredentials(mUsername, mPassword);
- CredentialsProvider credsProvider = mHttpClient.getCredentialsProvider();
- // setting AuthScope for 80 and 443, in case we end up getting redirected
- // from 80 to 443.
- credsProvider.setCredentials(new AuthScope(mHost, 80, AuthScope.ANY_REALM), creds);
- credsProvider.setCredentials(new AuthScope(mHost, 443, AuthScope.ANY_REALM), creds);
- credsProvider.setCredentials(new AuthScope(mHost, mUri.getPort(), AuthScope.ANY_REALM), creds);
- mHttpClient.setCredentialsProvider(credsProvider);
- }
-
return mHttpClient;
}
private InputStream sendRequest(String url, String method, StringEntity messageBody, HashMap headers, boolean tryAuth)
throws MessagingException
{
- WebDavHttpClient httpclient;
InputStream istream = null;
if (url == null ||
@@ -924,7 +864,7 @@ public class WebDavStore extends Store
return istream;
}
- httpclient = getHttpClient();
+ WebDavHttpClient httpclient = getHttpClient();
try
{
@@ -943,56 +883,46 @@ public class WebDavStore extends Store
httpmethod.setHeader(headerName, headers.get(headerName));
}
- if (mAuthString != null && mAuthenticated)
+ if (mAuthentication == AUTH_TYPE_NONE)
+ {
+ if (!tryAuth || !authenticate())
+ {
+ throw new MessagingException("Unable to authenticate in sendRequest().");
+ }
+ }
+ else if (mAuthentication == AUTH_TYPE_BASIC)
{
httpmethod.setHeader("Authorization", mAuthString);
}
httpmethod.setMethod(method);
- response = httpclient.executeOverride(httpmethod);
+ response = httpclient.executeOverride(httpmethod, mContext);
statusCode = response.getStatusLine().getStatusCode();
entity = response.getEntity();
if (statusCode == 401)
{
- if (tryAuth)
- {
- mAuthenticated = true;
- sendRequest(url, method, messageBody, headers, false);
- }
- else
- {
- throw new MessagingException("Invalid username or password for Basic authentication");
- }
+ throw new MessagingException("Invalid username or password for Basic authentication.");
}
else if (statusCode == 440)
{
- if (tryAuth)
+ if (tryAuth && mAuthentication == AUTH_TYPE_FORM_BASED)
{
- doFBA();
+ // Our cookie expired, re-authenticate.
+ doFBA(null);
sendRequest(url, method, messageBody, headers, false);
}
else
{
- throw new MessagingException("Authentication failure in sendRequest");
+ throw new MessagingException("Authentication failure in sendRequest().");
}
}
- else if (statusCode < 200 ||
- statusCode >= 300)
+ else if (statusCode < 200 || statusCode >= 300)
{
- throw new IOException("Error with code " + statusCode + " during request processing: "+
+ throw new IOException("Error with code " + statusCode + " during request processing: " +
response.getStatusLine().toString());
}
- else
- {
- if (tryAuth &&
- !mAuthenticated)
- {
- doFBA();
- sendRequest(url, method, messageBody, headers, false);
- }
- }
if (entity != null)
{
@@ -1284,10 +1214,9 @@ public class WebDavStore extends Store
Log.i(K9.LOG_TAG, "Moving " + messages.length + " messages to " + destFolder.mFolderUrl);
processRequest(mFolderUrl, action, messageBody, headers, false);
-
}
- private int getMessageCount(boolean read, CookieStore authCookies) throws MessagingException
+ private int getMessageCount(boolean read) throws MessagingException
{
String isRead;
int messageCount = 0;
@@ -1319,8 +1248,7 @@ public class WebDavStore extends Store
public int getMessageCount() throws MessagingException
{
open(OpenMode.READ_WRITE);
- this.mMessageCount = getMessageCount(true, WebDavStore.this.mAuthCookies);
-
+ this.mMessageCount = getMessageCount(true);
return this.mMessageCount;
}
@@ -1328,10 +1256,10 @@ public class WebDavStore extends Store
public int getUnreadMessageCount() throws MessagingException
{
open(OpenMode.READ_WRITE);
- this.mUnreadMessageCount = getMessageCount(false, WebDavStore.this.mAuthCookies);
-
+ this.mUnreadMessageCount = getMessageCount(false);
return this.mUnreadMessageCount;
}
+
@Override
public int getFlaggedMessageCount() throws MessagingException
{
@@ -1586,11 +1514,11 @@ public class WebDavStore extends Store
HttpEntity entity;
httpget.setHeader("translate", "f");
- if (mAuthString != null && mAuthenticated)
+ if (mAuthentication == AUTH_TYPE_BASIC)
{
httpget.setHeader("Authorization", mAuthString);
}
- response = httpclient.executeOverride(httpget);
+ response = httpclient.executeOverride(httpget, mContext);
statusCode = response.getStatusLine().getStatusCode();
@@ -1965,7 +1893,7 @@ public class WebDavStore extends Store
httpmethod.setHeader("Authorization", mAuthString);
}
- response = httpclient.executeOverride(httpmethod);
+ response = httpclient.executeOverride(httpmethod, mContext);
statusCode = response.getStatusLine().getStatusCode();
if (statusCode < 200 ||
@@ -2619,12 +2547,22 @@ public class WebDavStore extends Store
return responseStream;
}
-
- public HttpResponse executeOverride(HttpUriRequest request) throws IOException
+ public HttpResponse executeOverride(HttpUriRequest request, HttpContext context)
+ throws IOException
{
modifyRequestToAcceptGzipResponse(request);
- return super.execute(request);
+ return super.execute(request, context);
}
+ }
+ /**
+ * Simple data container for passing connection information.
+ */
+ private class ConnectionInfo
+ {
+ public int statusCode;
+ public short requiredAuthType;
+ public String guessedAuthUrl;
+ public String redirectUrl;
}
}
diff --git a/src/com/fsck/k9/mail/transport/WebDavTransport.java b/src/com/fsck/k9/mail/transport/WebDavTransport.java
index 0d78ab353..0790cc2a6 100644
--- a/src/com/fsck/k9/mail/transport/WebDavTransport.java
+++ b/src/com/fsck/k9/mail/transport/WebDavTransport.java
@@ -8,41 +8,23 @@ import com.fsck.k9.K9;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Transport;
-import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.store.WebDavStore;
-import java.io.OutputStream;
-import java.net.Socket;
-
public class WebDavTransport extends Transport
{
- public static final int CONNECTION_SECURITY_NONE = 0;
- public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
- public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
- public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
- public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
-
- String host;
- int mPort;
-
- boolean mSecure;
- Socket mSocket;
- PeekableInputStream mIn;
- OutputStream mOut;
private WebDavStore store;
- /**
- * 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
- *
- * @param _uri
- */
public WebDavTransport(Account account) throws MessagingException
{
- store = new WebDavStore(account);
+ if (account.getRemoteStore() instanceof WebDavStore)
+ {
+ store = (WebDavStore) account.getRemoteStore();
+ }
+ else
+ {
+ store = new WebDavStore(account);
+ }
+
if (K9.DEBUG)
Log.d(K9.LOG_TAG, ">>> New WebDavTransport creation complete");
}
@@ -64,10 +46,6 @@ public class WebDavTransport extends Transport
@Override
public void sendMessage(Message message) throws MessagingException
{
-
store.sendMessages(new Message[] { message });
-
-
}
-
}