summaryrefslogtreecommitdiffstats
path: root/src/com/android/phone/common/mail/store/ImapConnection.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/phone/common/mail/store/ImapConnection.java')
-rw-r--r--src/com/android/phone/common/mail/store/ImapConnection.java248
1 files changed, 248 insertions, 0 deletions
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
new file mode 100644
index 0000000..3cae5f8
--- /dev/null
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.phone.common.mail.store;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.phone.common.mail.AuthenticationFailedException;
+import com.android.phone.common.mail.CertificateValidationException;
+import com.android.phone.common.mail.MailTransport;
+import com.android.phone.common.mail.MessagingException;
+import com.android.phone.common.mail.store.imap.ImapConstants;
+import com.android.phone.common.mail.store.imap.ImapResponse;
+import com.android.phone.common.mail.store.imap.ImapResponseParser;
+import com.android.phone.common.mail.store.imap.ImapUtility;
+import com.android.phone.common.mail.store.ImapStore.ImapException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.SSLException;
+
+/**
+ * A cacheable class that stores the details for a single IMAP connection.
+ */
+public class ImapConnection {
+ private final String TAG = "ImapConnection";
+
+ private String mLoginPhrase;
+ private ImapStore mImapStore;
+ private MailTransport mTransport;
+ private ImapResponseParser mParser;
+
+ static final String IMAP_REDACTED_LOG = "[IMAP command redacted]";
+
+ /**
+ * Next tag to use. All connections associated to the same ImapStore instance share the same
+ * counter to make tests simpler.
+ * (Some of the tests involve multiple connections but only have a single counter to track the
+ * tag.)
+ */
+ private final AtomicInteger mNextCommandTag = new AtomicInteger(0);
+
+ ImapConnection(ImapStore store) {
+ setStore(store);
+ }
+
+ void setStore(ImapStore store) {
+ // TODO: maybe we should throw an exception if the connection is not closed here,
+ // if it's not currently closed, then we won't reopen it, so if the credentials have
+ // changed, the connection will not be reestablished.
+ mImapStore = store;
+ mLoginPhrase = null;
+ }
+
+ /**
+ * Generates and returns the phrase to be used for authentication. This will be a LOGIN with
+ * username and password.
+ *
+ * @return the login command string to sent to the IMAP server
+ */
+ String getLoginPhrase() {
+ if (mLoginPhrase == null) {
+ if (mImapStore.getUsername() != null && mImapStore.getPassword() != null) {
+ // build the LOGIN string once (instead of over-and-over again.)
+ // apply the quoting here around the built-up password
+ mLoginPhrase = ImapConstants.LOGIN + " " + mImapStore.getUsername() + " "
+ + ImapUtility.imapQuoted(mImapStore.getPassword());
+ }
+ }
+ return mLoginPhrase;
+ }
+
+ void open() throws IOException, MessagingException {
+ if (mTransport != null && mTransport.isOpen()) {
+ return;
+ }
+
+ try {
+ // copy configuration into a clean transport, if necessary
+ if (mTransport == null) {
+ mTransport = mImapStore.cloneTransport();
+ }
+
+ mTransport.open();
+
+ createParser();
+
+ // LOGIN
+ doLogin();
+ } catch (SSLException e) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "SSLException ", e);
+ }
+ throw new CertificateValidationException(e.getMessage(), e);
+ } catch (IOException ioe) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "IOException", ioe);
+ }
+ throw ioe;
+ } finally {
+ destroyResponses();
+ }
+ }
+
+ /**
+ * Closes the connection and releases all resources. This connection can not be used again
+ * until {@link #setStore(ImapStore)} is called.
+ */
+ void close() {
+ if (mTransport != null) {
+ mTransport.close();
+ mTransport = null;
+ }
+ destroyResponses();
+ mParser = null;
+ mImapStore = null;
+ }
+
+ /**
+ * Logs into the IMAP server
+ */
+ private void doLogin() throws IOException, MessagingException, AuthenticationFailedException {
+ try {
+ executeSimpleCommand(getLoginPhrase(), true);
+ } catch (ImapException ie) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "ImapException", ie);
+ }
+ final String status = ie.getStatus();
+ final String code = ie.getResponseCode();
+ final String alertText = ie.getAlertText();
+
+ // if the response code indicates expired or bad credentials, throw a special exception
+ if (ImapConstants.AUTHENTICATIONFAILED.equals(code) ||
+ ImapConstants.EXPIRED.equals(code) ||
+ (ImapConstants.NO.equals(status) && TextUtils.isEmpty(code))) {
+ throw new AuthenticationFailedException(alertText, ie);
+ }
+
+ throw new MessagingException(alertText, ie);
+ }
+ }
+
+ /**
+ * Create an {@link ImapResponseParser} from {@code mTransport.getInputStream()} and
+ * set it to {@link #mParser}.
+ *
+ * If we already have an {@link ImapResponseParser}, we
+ * {@link #destroyResponses()} and throw it away.
+ */
+ private void createParser() {
+ destroyResponses();
+ mParser = new ImapResponseParser(mTransport.getInputStream());
+ }
+
+
+ void destroyResponses() {
+ if (mParser != null) {
+ mParser.destroyResponses();
+ }
+ }
+
+ List<ImapResponse> executeSimpleCommand(String command)
+ throws IOException, MessagingException{
+ return executeSimpleCommand(command, false);
+ }
+
+ /**
+ * Send a single command to the server. The command will be preceded by an IMAP command
+ * tag and followed by \r\n (caller need not supply them).
+ * Execute a simple command at the server, a simple command being one that is sent in a single
+ * line of text
+ *
+ * @param command the command to send to the server
+ * @param sensitive whether the command should be redacted in logs (used for login)
+ * @return a list of ImapResponses
+ * @throws IOException
+ * @throws MessagingException
+ */
+ List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
+ throws IOException, MessagingException {
+ // TODO: It may be nice to catch IOExceptions and close the connection here.
+ // Currently, we expect callers to do that, but if they fail to we'll be in a broken state.
+ sendCommand(command, sensitive);
+ return getCommandResponses();
+ }
+
+ String sendCommand(String command, boolean sensitive) throws IOException, MessagingException {
+ open();
+
+ if (mTransport == null) {
+ throw new IOException("Null transport");
+ }
+ String tag = Integer.toString(mNextCommandTag.incrementAndGet());
+ String commandToSend = tag + " " + command;
+ mTransport.writeLine(commandToSend, (sensitive ? IMAP_REDACTED_LOG : command));
+
+ return tag;
+ }
+
+ /**
+ * Read and return all of the responses from the most recent command sent to the server
+ *
+ * @return a list of ImapResponses
+ * @throws IOException
+ * @throws MessagingException
+ */
+ List<ImapResponse> getCommandResponses() throws IOException, MessagingException {
+ final List<ImapResponse> responses = new ArrayList<ImapResponse>();
+ ImapResponse response;
+ do {
+ response = mParser.readResponse();
+ responses.add(response);
+ } while (!response.isTagged());
+
+ if (!response.isOk()) {
+ final String toString = response.toString();
+ final String status = response.getStatusOrEmpty().getString();
+ final String alert = response.getAlertTextOrEmpty().getString();
+ final String responseCode = response.getResponseCodeOrEmpty().getString();
+ destroyResponses();
+
+ // if the response code indicates an error occurred within the server, indicate that
+ if (ImapConstants.UNAVAILABLE.equals(responseCode)) {
+ throw new MessagingException(MessagingException.SERVER_ERROR, alert);
+ }
+
+ throw new ImapException(toString, status, alert, responseCode);
+ }
+ return responses;
+ }
+}