From e1bb378b86d81791732d7355e988d1b5fe825383 Mon Sep 17 00:00:00 2001 From: Nancy Chen Date: Thu, 26 Mar 2015 17:20:44 -0700 Subject: Move files from PhoneCommon to services/Telephony Since PhoneCommon is a library used by GoogleContacts, etc. we don't want to clutter it with Telephony-specific code. Bug: 19236241 Change-Id: I063640cda141cbb9261d25e23c95f7016369eb9a --- .../common/mail/AuthenticationFailedException.java | 33 -- .../mail/CertificateValidationException.java | 29 -- .../phone/common/mail/FixedLengthInputStream.java | 79 ---- .../android/phone/common/mail/MailTransport.java | 247 ------------ src/com/android/phone/common/mail/Message.java | 24 -- .../phone/common/mail/MessagingException.java | 139 ------- .../phone/common/mail/PeekableInputStream.java | 80 ---- .../phone/common/mail/store/ImapConnection.java | 248 ------------ .../phone/common/mail/store/ImapFolder.java | 165 -------- .../android/phone/common/mail/store/ImapStore.java | 113 ------ .../common/mail/store/imap/ImapConstants.java | 97 ----- .../phone/common/mail/store/imap/ImapElement.java | 120 ------ .../phone/common/mail/store/imap/ImapList.java | 235 ----------- .../common/mail/store/imap/ImapMemoryLiteral.java | 77 ---- .../phone/common/mail/store/imap/ImapResponse.java | 158 -------- .../common/mail/store/imap/ImapResponseParser.java | 434 --------------------- .../common/mail/store/imap/ImapSimpleString.java | 62 --- .../phone/common/mail/store/imap/ImapString.java | 185 --------- .../mail/store/imap/ImapTempFileLiteral.java | 203 ---------- .../phone/common/mail/store/imap/ImapUtility.java | 51 --- 20 files changed, 2779 deletions(-) delete mode 100644 src/com/android/phone/common/mail/AuthenticationFailedException.java delete mode 100644 src/com/android/phone/common/mail/CertificateValidationException.java delete mode 100644 src/com/android/phone/common/mail/FixedLengthInputStream.java delete mode 100644 src/com/android/phone/common/mail/MailTransport.java delete mode 100644 src/com/android/phone/common/mail/Message.java delete mode 100644 src/com/android/phone/common/mail/MessagingException.java delete mode 100644 src/com/android/phone/common/mail/PeekableInputStream.java delete mode 100644 src/com/android/phone/common/mail/store/ImapConnection.java delete mode 100644 src/com/android/phone/common/mail/store/ImapFolder.java delete mode 100644 src/com/android/phone/common/mail/store/ImapStore.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapConstants.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapElement.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapList.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapMemoryLiteral.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapResponse.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapResponseParser.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapSimpleString.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapString.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapTempFileLiteral.java delete mode 100644 src/com/android/phone/common/mail/store/imap/ImapUtility.java (limited to 'src/com/android') diff --git a/src/com/android/phone/common/mail/AuthenticationFailedException.java b/src/com/android/phone/common/mail/AuthenticationFailedException.java deleted file mode 100644 index f13ab45..0000000 --- a/src/com/android/phone/common/mail/AuthenticationFailedException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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; - -public class AuthenticationFailedException extends MessagingException { - public static final long serialVersionUID = -1; - - public AuthenticationFailedException(String message) { - super(MessagingException.AUTHENTICATION_FAILED, message); - } - - public AuthenticationFailedException(int exceptionType, String message) { - super(exceptionType, message); - } - - public AuthenticationFailedException(String message, Throwable throwable) { - super(MessagingException.AUTHENTICATION_FAILED, message, throwable); - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/CertificateValidationException.java b/src/com/android/phone/common/mail/CertificateValidationException.java deleted file mode 100644 index d8ae9b4..0000000 --- a/src/com/android/phone/common/mail/CertificateValidationException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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; - -public class CertificateValidationException extends MessagingException { - public static final long serialVersionUID = -1; - - public CertificateValidationException(String message) { - super(MessagingException.CERTIFICATE_VALIDATION_ERROR, message); - } - - public CertificateValidationException(String message, Throwable throwable) { - super(MessagingException.CERTIFICATE_VALIDATION_ERROR, message, throwable); - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/FixedLengthInputStream.java b/src/com/android/phone/common/mail/FixedLengthInputStream.java deleted file mode 100644 index 499feca..0000000 --- a/src/com/android/phone/common/mail/FixedLengthInputStream.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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; - -import java.io.IOException; -import java.io.InputStream; - -/** - * A filtering InputStream that stops allowing reads after the given length has been read. This - * is used to allow a client to read directly from an underlying protocol stream without reading - * past where the protocol handler intended the client to read. - */ -public class FixedLengthInputStream extends InputStream { - private final InputStream mIn; - private final int mLength; - private int mCount; - - public FixedLengthInputStream(InputStream in, int length) { - this.mIn = in; - this.mLength = length; - } - - @Override - public int available() throws IOException { - return mLength - mCount; - } - - @Override - public int read() throws IOException { - if (mCount < mLength) { - mCount++; - return mIn.read(); - } else { - return -1; - } - } - - @Override - public int read(byte[] b, int offset, int length) throws IOException { - if (mCount < mLength) { - int d = mIn.read(b, offset, Math.min(mLength - mCount, length)); - if (d == -1) { - return -1; - } else { - mCount += d; - return d; - } - } else { - return -1; - } - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - public int getLength() { - return mLength; - } - - @Override - public String toString() { - return String.format("FixedLengthInputStream(in=%s, length=%d)", mIn.toString(), mLength); - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/MailTransport.java b/src/com/android/phone/common/mail/MailTransport.java deleted file mode 100644 index 5e3f7f5..0000000 --- a/src/com/android/phone/common/mail/MailTransport.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.util.Log; - -import com.android.phone.common.mail.store.ImapStore; - -import java.net.SocketAddress; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.Socket; - -/** - * Make connection and perform operations on mail server by reading and writing lines. - */ -public class MailTransport { - private static final String TAG = "MailTransport"; - - // TODO protected eventually - /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000; - /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000; - - private static final HostnameVerifier HOSTNAME_VERIFIER = - HttpsURLConnection.getDefaultHostnameVerifier(); - - private Context mContext; - private String mHost; - private int mPort; - private Socket mSocket; - private BufferedInputStream mIn; - private BufferedOutputStream mOut; - private int mFlags; - - public MailTransport(Context context, String address, int port, int flags) { - mContext = context; - mHost = address; - mPort = port; - mFlags = flags; - } - - /** - * Returns a new transport, using the current transport as a model. The new transport is - * configured identically, but not opened or connected in any way. - */ - @Override - public MailTransport clone() { - return new MailTransport(mContext, mHost, mPort, mFlags); - } - - public boolean canTrySslSecurity() { - return (mFlags & ImapStore.FLAG_SSL) != 0; - } - - public boolean canTrustAllCertificates() { - return (mFlags & ImapStore.FLAG_TRUST_ALL) != 0; - } - - /** - * Attempts to open a connection using the Uri supplied for connection parameters. Will attempt - * an SSL connection if indicated. - */ - public void open() throws MessagingException, CertificateValidationException { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "*** IMAP open " + mHost + ":" + String.valueOf(mPort)); - } - - try { - SocketAddress socketAddress = new InetSocketAddress(mHost, mPort); - if (canTrySslSecurity()) { - mSocket = HttpsURLConnection.getDefaultSSLSocketFactory().createSocket(); - } else { - mSocket = new Socket(); - } - mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); - // After the socket connects to an SSL server, confirm that the hostname is as expected - if (canTrySslSecurity() && !canTrustAllCertificates()) { - verifyHostname(mSocket, mHost); - } - - mIn = new BufferedInputStream(mSocket.getInputStream(), 1024); - mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512); - mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); - } catch (SSLException e) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, e.toString()); - } - throw new CertificateValidationException(e.getMessage(), e); - } catch (IOException ioe) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, ioe.toString()); - } - throw new MessagingException(MessagingException.IOERROR, ioe.toString()); - } catch (IllegalArgumentException iae) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, iae.toString()); - } - throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION, iae.toString()); - } - } - - /** - * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this - * service but is not in the public API. - * - * Verify the hostname of the certificate used by the other end of a - * connected socket. It is harmless to call this method redundantly if the hostname has already - * been verified. - * - *

Wildcard certificates are allowed to verify any matching hostname, - * so "foo.bar.example.com" is verified if the peer has a certificate - * for "*.example.com". - * - * @param socket An SSL socket which has been connected to a server - * @param hostname The expected hostname of the remote server - * @throws IOException if something goes wrong handshaking with the server - * @throws SSLPeerUnverifiedException if the server cannot prove its identity - */ - private static void verifyHostname(Socket socket, String hostname) throws IOException { - // The code at the start of OpenSSLSocketImpl.startHandshake() - // ensures that the call is idempotent, so we can safely call it. - SSLSocket ssl = (SSLSocket) socket; - ssl.startHandshake(); - - SSLSession session = ssl.getSession(); - if (session == null) { - throw new SSLException("Cannot verify SSL socket without session"); - } - // TODO: Instead of reporting the name of the server we think we're connecting to, - // we should be reporting the bad name in the certificate. Unfortunately this is buried - // in the verifier code and is not available in the verifier API, and extracting the - // CN & alts is beyond the scope of this patch. - if (!HOSTNAME_VERIFIER.verify(hostname, session)) { - throw new SSLPeerUnverifiedException( - "Certificate hostname not useable for server: " + hostname); - } - } - - public boolean isOpen() { - return (mIn != null && mOut != null && - mSocket != null && mSocket.isConnected() && !mSocket.isClosed()); - } - - /** - * Close the connection. MUST NOT return any exceptions - must be "best effort" and safe. - */ - public void close() { - try { - mIn.close(); - } catch (Exception e) { - // May fail if the connection is already closed. - } - try { - mOut.close(); - } catch (Exception e) { - // May fail if the connection is already closed. - } - try { - mSocket.close(); - } catch (Exception e) { - // May fail if the connection is already closed. - } - mIn = null; - mOut = null; - mSocket = null; - } - - public InputStream getInputStream() { - return mIn; - } - - public OutputStream getOutputStream() { - return mOut; - } - - /** - * Writes a single line to the server using \r\n termination. - */ - public void writeLine(String s, String sensitiveReplacement) throws IOException { - if (Log.isLoggable(TAG, Log.DEBUG)) { - if (sensitiveReplacement != null) { - Log.d(TAG, ">>> " + sensitiveReplacement); - } else { - Log.d(TAG, ">>> " + s); - } - } - - OutputStream out = getOutputStream(); - out.write(s.getBytes()); - out.write('\r'); - out.write('\n'); - out.flush(); - } - - /** - * Reads a single line from the server, using either \r\n or \n as the delimiter. The - * delimiter char(s) are not included in the result. - */ - public String readLine(boolean loggable) throws IOException { - StringBuffer sb = new StringBuffer(); - InputStream in = getInputStream(); - int d; - while ((d = in.read()) != -1) { - if (((char)d) == '\r') { - continue; - } else if (((char)d) == '\n') { - break; - } else { - sb.append((char)d); - } - } - if (d == -1 && Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "End of stream reached while trying to read line."); - } - String ret = sb.toString(); - if (loggable && Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "<<< " + ret); - } - return ret; - } -} diff --git a/src/com/android/phone/common/mail/Message.java b/src/com/android/phone/common/mail/Message.java deleted file mode 100644 index cd5a44d..0000000 --- a/src/com/android/phone/common/mail/Message.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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; - -/** - * Object to represent an email message. - */ -public class Message { - public static final String FLAG_SEEN = "seen"; - public static final String FLAG_DELETED = "deleted"; -} diff --git a/src/com/android/phone/common/mail/MessagingException.java b/src/com/android/phone/common/mail/MessagingException.java deleted file mode 100644 index e4c674a..0000000 --- a/src/com/android/phone/common/mail/MessagingException.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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; - -/** - * This exception is used for most types of failures that occur during server interactions. - * - * Data passed through this exception should be considered non-localized. Any strings should - * either be internal-only (for debugging) or server-generated. - * - * TO DO: Does it make sense to further collapse AuthenticationFailedException and - * CertificateValidationException and any others into this? - */ -public class MessagingException extends Exception { - public static final long serialVersionUID = -1; - - public static final int NO_ERROR = -1; - /** Any exception that does not specify a specific issue */ - public static final int UNSPECIFIED_EXCEPTION = 0; - /** Connection or IO errors */ - public static final int IOERROR = 1; - /** The configuration requested TLS but the server did not support it. */ - public static final int TLS_REQUIRED = 2; - /** Authentication is required but the server did not support it. */ - public static final int AUTH_REQUIRED = 3; - /** General security failures */ - public static final int GENERAL_SECURITY = 4; - /** Authentication failed */ - public static final int AUTHENTICATION_FAILED = 5; - /** Attempt to create duplicate account */ - public static final int DUPLICATE_ACCOUNT = 6; - /** Required security policies reported - advisory only */ - public static final int SECURITY_POLICIES_REQUIRED = 7; - /** Required security policies not supported */ - public static final int SECURITY_POLICIES_UNSUPPORTED = 8; - /** The protocol (or protocol version) isn't supported */ - public static final int PROTOCOL_VERSION_UNSUPPORTED = 9; - /** The server's SSL certificate couldn't be validated */ - public static final int CERTIFICATE_VALIDATION_ERROR = 10; - /** Authentication failed during autodiscover */ - public static final int AUTODISCOVER_AUTHENTICATION_FAILED = 11; - /** Autodiscover completed with a result (non-error) */ - public static final int AUTODISCOVER_AUTHENTICATION_RESULT = 12; - /** Ambiguous failure; server error or bad credentials */ - public static final int AUTHENTICATION_FAILED_OR_SERVER_ERROR = 13; - /** The server refused access */ - public static final int ACCESS_DENIED = 14; - /** The server refused access */ - public static final int ATTACHMENT_NOT_FOUND = 15; - /** A client SSL certificate is required for connections to the server */ - public static final int CLIENT_CERTIFICATE_REQUIRED = 16; - /** The client SSL certificate specified is invalid */ - public static final int CLIENT_CERTIFICATE_ERROR = 17; - /** The server indicates it does not support OAuth authentication */ - public static final int OAUTH_NOT_SUPPORTED = 18; - /** The server indicates it experienced an internal error */ - public static final int SERVER_ERROR = 19; - - protected int mExceptionType; - // Exception type-specific data - protected Object mExceptionData; - - public MessagingException(String message, Throwable throwable) { - this(UNSPECIFIED_EXCEPTION, message, throwable); - } - - public MessagingException(int exceptionType, String message, Throwable throwable) { - super(message, throwable); - mExceptionType = exceptionType; - mExceptionData = null; - } - - /** - * Constructs a MessagingException with an exceptionType and a null message. - * @param exceptionType The exception type to set for this exception. - */ - public MessagingException(int exceptionType) { - this(exceptionType, null, null); - } - - /** - * Constructs a MessagingException with a message. - * @param message the message for this exception - */ - public MessagingException(String message) { - this(UNSPECIFIED_EXCEPTION, message, null); - } - - /** - * Constructs a MessagingException with an exceptionType and a message. - * @param exceptionType The exception type to set for this exception. - */ - public MessagingException(int exceptionType, String message) { - this(exceptionType, message, null); - } - - /** - * Constructs a MessagingException with an exceptionType, a message, and data - * @param exceptionType The exception type to set for this exception. - * @param message the message for the exception (or null) - * @param data exception-type specific data for the exception (or null) - */ - public MessagingException(int exceptionType, String message, Object data) { - super(message); - mExceptionType = exceptionType; - mExceptionData = data; - } - - /** - * Return the exception type. Will be OTHER_EXCEPTION if not explicitly set. - * - * @return Returns the exception type. - */ - public int getExceptionType() { - return mExceptionType; - } - /** - * Return the exception data. Will be null if not explicitly set. - * - * @return Returns the exception data. - */ - public Object getExceptionData() { - return mExceptionData; - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/PeekableInputStream.java b/src/com/android/phone/common/mail/PeekableInputStream.java deleted file mode 100644 index d29fc6b..0000000 --- a/src/com/android/phone/common/mail/PeekableInputStream.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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; - -import java.io.IOException; -import java.io.InputStream; - -/** - * A filtering InputStream that allows single byte "peeks" without consuming the byte. The - * client of this stream can call peek() to see the next available byte in the stream - * and a subsequent read will still return the peeked byte. - */ -public class PeekableInputStream extends InputStream { - private final InputStream mIn; - private boolean mPeeked; - private int mPeekedByte; - - public PeekableInputStream(InputStream in) { - this.mIn = in; - } - - @Override - public int read() throws IOException { - if (!mPeeked) { - return mIn.read(); - } else { - mPeeked = false; - return mPeekedByte; - } - } - - public int peek() throws IOException { - if (!mPeeked) { - mPeekedByte = read(); - mPeeked = true; - } - return mPeekedByte; - } - - @Override - public int read(byte[] b, int offset, int length) throws IOException { - if (!mPeeked) { - return mIn.read(b, offset, length); - } else { - b[0] = (byte)mPeekedByte; - mPeeked = false; - int r = mIn.read(b, offset + 1, length - 1); - if (r == -1) { - return 1; - } else { - return r + 1; - } - } - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - @Override - public String toString() { - return String.format("PeekableInputStream(in=%s, peeked=%b, peekedByte=%d)", - mIn.toString(), mPeeked, mPeekedByte); - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java deleted file mode 100644 index 3cae5f8..0000000 --- a/src/com/android/phone/common/mail/store/ImapConnection.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * 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 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 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 getCommandResponses() throws IOException, MessagingException { - final List responses = new ArrayList(); - 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; - } -} diff --git a/src/com/android/phone/common/mail/store/ImapFolder.java b/src/com/android/phone/common/mail/store/ImapFolder.java deleted file mode 100644 index 9f7db4a..0000000 --- a/src/com/android/phone/common/mail/store/ImapFolder.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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.util.Log; - -import com.android.phone.common.mail.AuthenticationFailedException; -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.ImapString; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -/** - * Represents one folder on the IMAP server. - */ -public class ImapFolder { - private final String TAG = "ImapFolder"; - private ImapStore mStore; - private String mName; - private String mMode; - private boolean mExists; - private ImapConnection mConnection; - private int mMessageCount; - - public static final String MODE_READ_ONLY = "mode_read_only"; - public static final String MODE_READ_WRITE = "mode_read_write"; - - public ImapFolder(ImapStore store, String name) { - mStore = store; - mName = name; - } - - private void destroyResponses() { - if (mConnection != null) { - mConnection.destroyResponses(); - } - } - - public void open(String mode) - throws MessagingException { - try { - if (isOpen()) { - if (mMode == mode) { - // Make sure the connection is valid. - // If it's not we'll close it down and continue on to get a new one. - try { - mConnection.executeSimpleCommand(ImapConstants.NOOP); - return; - - } catch (IOException ioe) { - ioExceptionHandler(mConnection, ioe); - } finally { - destroyResponses(); - } - } else { - // Return the connection to the pool, if exists. - close(false); - } - } - synchronized (this) { - mConnection = mStore.getConnection(); - } - // * FLAGS (\Answered \Flagged \Deleted \Seen \Draft NonJunk - // $MDNSent) - // * OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft - // NonJunk $MDNSent \*)] Flags permitted. - // * 23 EXISTS - // * 0 RECENT - // * OK [UIDVALIDITY 1125022061] UIDs valid - // * OK [UIDNEXT 57576] Predicted next UID - // 2 OK [READ-WRITE] Select completed. - try { - doSelect(); - } catch (IOException ioe) { - throw ioExceptionHandler(mConnection, ioe); - } finally { - destroyResponses(); - } - } catch (AuthenticationFailedException e) { - // Don't cache this connection, so we're forced to try connecting/login again - mConnection = null; - close(false); - throw e; - } catch (MessagingException e) { - mExists = false; - close(false); - throw e; - } - } - - public boolean isOpen() { - return mExists && mConnection != null; - } - - /** - * Selects the folder for use. Before performing any operations on this folder, it - * must be selected. - */ - private void doSelect() throws IOException, MessagingException { - final List responses = mConnection.executeSimpleCommand( - String.format(Locale.US, ImapConstants.SELECT + " \"%s\"", mName)); - - // Assume the folder is opened read-write; unless we are notified otherwise - mMode = MODE_READ_WRITE; - int messageCount = -1; - for (ImapResponse response : responses) { - if (response.isDataResponse(1, ImapConstants.EXISTS)) { - messageCount = response.getStringOrEmpty(0).getNumberOrZero(); - } else if (response.isOk()) { - final ImapString responseCode = response.getResponseCodeOrEmpty(); - if (responseCode.is(ImapConstants.READ_ONLY)) { - mMode = MODE_READ_ONLY; - } else if (responseCode.is(ImapConstants.READ_WRITE)) { - mMode = MODE_READ_WRITE; - } - } else if (response.isTagged()) { // Not OK - throw new MessagingException("Can't open mailbox: " - + response.getStatusResponseTextOrEmpty()); - } - } - if (messageCount == -1) { - throw new MessagingException("Did not find message count during select"); - } - mMessageCount = messageCount; - mExists = true; - } - - public void close(boolean expunge) { - // TODO implement expunge - mMessageCount = -1; - synchronized (this) { - mStore.closeConnection(); - mConnection = null; - } - } - - private MessagingException ioExceptionHandler(ImapConnection connection, IOException ioe) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "IO Exception detected: ", ioe); - } - connection.close(); - if (connection == mConnection) { - mConnection = null; // To prevent close() from returning the connection to the pool. - close(false); - } - return new MessagingException(MessagingException.IOERROR, "IO Error", ioe); - } -} diff --git a/src/com/android/phone/common/mail/store/ImapStore.java b/src/com/android/phone/common/mail/store/ImapStore.java deleted file mode 100644 index 38bb62f..0000000 --- a/src/com/android/phone/common/mail/store/ImapStore.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.content.Context; - -import com.android.phone.common.mail.MailTransport; -import com.android.phone.common.mail.MessagingException; -import com.android.phone.common.mail.store.ImapFolder; - -public class ImapStore { - /** - * A global suggestion to Store implementors on how much of the body - * should be returned on FetchProfile.Item.BODY_SANE requests. We'll use 125k now. - */ - public static final int FETCH_BODY_SANE_SUGGESTED_SIZE = (125 * 1024); - private Context mContext; - private String mUsername; - private String mPassword; - private MailTransport mTransport; - private ImapConnection mConnection; - - public static final int FLAG_NONE = 0x00; // No flags - public static final int FLAG_SSL = 0x01; // Use SSL - public static final int FLAG_TLS = 0x02; // Use TLS - public static final int FLAG_AUTHENTICATE = 0x04; // Use name/password for authentication - public static final int FLAG_TRUST_ALL = 0x08; // Trust all certificates - public static final int FLAG_OAUTH = 0x10; // Use OAuth for authentication - - /** - * Contains all the information necessary to log into an imap server - */ - public ImapStore(Context context, String username, String password, int port, - String serverName, int flags) { - mContext = context; - mUsername = username; - mPassword = password; - mTransport = new MailTransport(context, serverName, port, flags); - } - - public ImapFolder getFolder(String name) { - return new ImapFolder(this, name); - } - - public String getUsername() { - return mUsername; - } - - public String getPassword() { - return mPassword; - } - - /** Returns a clone of the transport associated with this store. */ - MailTransport cloneTransport() { - return mTransport.clone(); - } - - static class ImapException extends MessagingException { - private static final long serialVersionUID = 1L; - - private final String mStatus; - private final String mAlertText; - private final String mResponseCode; - - public ImapException(String message, String status, String alertText, - String responseCode) { - super(message); - mStatus = status; - mAlertText = alertText; - mResponseCode = responseCode; - } - - public String getStatus() { - return mStatus; - } - - public String getAlertText() { - return mAlertText; - } - - public String getResponseCode() { - return mResponseCode; - } - } - - public void closeConnection() { - if (mConnection != null) { - mConnection.close(); - mConnection = null; - } - } - - public ImapConnection getConnection() { - if (mConnection == null) { - mConnection = new ImapConnection(this); - } - return mConnection; - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapConstants.java b/src/com/android/phone/common/mail/store/imap/ImapConstants.java deleted file mode 100644 index 0f1f526..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapConstants.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.imap; - -import com.android.phone.common.mail.store.ImapStore; - -public final class ImapConstants { - private ImapConstants() {} - - public static final String FETCH_FIELD_BODY_PEEK_BARE = "BODY.PEEK"; - public static final String FETCH_FIELD_BODY_PEEK = FETCH_FIELD_BODY_PEEK_BARE + "[]"; - public static final String FETCH_FIELD_BODY_PEEK_SANE - = String.format("BODY.PEEK[]<0.%d>", ImapStore.FETCH_BODY_SANE_SUGGESTED_SIZE); - public static final String FETCH_FIELD_HEADERS = - "BODY.PEEK[HEADER.FIELDS (date subject from content-type to cc message-id)]"; - - public static final String ALERT = "ALERT"; - public static final String APPEND = "APPEND"; - public static final String BAD = "BAD"; - public static final String BADCHARSET = "BADCHARSET"; - public static final String BODY = "BODY"; - public static final String BODY_BRACKET_HEADER = "BODY[HEADER"; - public static final String BODYSTRUCTURE = "BODYSTRUCTURE"; - public static final String BYE = "BYE"; - public static final String CAPABILITY = "CAPABILITY"; - public static final String CHECK = "CHECK"; - public static final String CLOSE = "CLOSE"; - public static final String COPY = "COPY"; - public static final String CREATE = "CREATE"; - public static final String DELETE = "DELETE"; - public static final String EXAMINE = "EXAMINE"; - public static final String EXISTS = "EXISTS"; - public static final String EXPUNGE = "EXPUNGE"; - public static final String FETCH = "FETCH"; - public static final String FLAG_ANSWERED = "\\ANSWERED"; - public static final String FLAG_DELETED = "\\DELETED"; - public static final String FLAG_FLAGGED = "\\FLAGGED"; - public static final String FLAG_NO_SELECT = "\\NOSELECT"; - public static final String FLAG_SEEN = "\\SEEN"; - public static final String FLAGS = "FLAGS"; - public static final String FLAGS_SILENT = "FLAGS.SILENT"; - public static final String ID = "ID"; - public static final String INBOX = "INBOX"; - public static final String INTERNALDATE = "INTERNALDATE"; - public static final String LIST = "LIST"; - public static final String LOGIN = "LOGIN"; - public static final String LOGOUT = "LOGOUT"; - public static final String LSUB = "LSUB"; - public static final String NO = "NO"; - public static final String NOOP = "NOOP"; - public static final String OK = "OK"; - public static final String PARSE = "PARSE"; - public static final String PERMANENTFLAGS = "PERMANENTFLAGS"; - public static final String PREAUTH = "PREAUTH"; - public static final String READ_ONLY = "READ-ONLY"; - public static final String READ_WRITE = "READ-WRITE"; - public static final String RENAME = "RENAME"; - public static final String RFC822_SIZE = "RFC822.SIZE"; - public static final String SEARCH = "SEARCH"; - public static final String SELECT = "SELECT"; - public static final String STARTTLS = "STARTTLS"; - public static final String STATUS = "STATUS"; - public static final String STORE = "STORE"; - public static final String SUBSCRIBE = "SUBSCRIBE"; - public static final String TEXT = "TEXT"; - public static final String TRYCREATE = "TRYCREATE"; - public static final String UID = "UID"; - public static final String UID_COPY = "UID COPY"; - public static final String UID_FETCH = "UID FETCH"; - public static final String UID_SEARCH = "UID SEARCH"; - public static final String UID_STORE = "UID STORE"; - public static final String UIDNEXT = "UIDNEXT"; - public static final String UIDVALIDITY = "UIDVALIDITY"; - public static final String UNSEEN = "UNSEEN"; - public static final String UNSUBSCRIBE = "UNSUBSCRIBE"; - public static final String APPENDUID = "APPENDUID"; - public static final String NIL = "NIL"; - - /** response codes within IMAP responses */ - public static final String EXPIRED = "EXPIRED"; - public static final String AUTHENTICATIONFAILED = "AUTHENTICATIONFAILED"; - public static final String UNAVAILABLE = "UNAVAILABLE"; -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapElement.java b/src/com/android/phone/common/mail/store/imap/ImapElement.java deleted file mode 100644 index 2d1824e..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapElement.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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.imap; - -/** - * Class representing "element"s in IMAP responses. - * - *

Class hierarchy: - *

- * ImapElement
- *   |
- *   |-- ImapElement.NONE (for 'index out of range')
- *   |
- *   |-- ImapList (isList() == true)
- *   |   |
- *   |   |-- ImapList.EMPTY
- *   |   |
- *   |   --- ImapResponse
- *   |
- *   --- ImapString (isString() == true)
- *       |
- *       |-- ImapString.EMPTY
- *       |
- *       |-- ImapSimpleString
- *       |
- *       |-- ImapMemoryLiteral
- *       |
- *       --- ImapTempFileLiteral
- * 
- */ -public abstract class ImapElement { - /** - * An element that is returned by {@link ImapList#getElementOrNone} to indicate an index - * is out of range. - */ - public static final ImapElement NONE = new ImapElement() { - @Override public void destroy() { - // Don't call super.destroy(). - // It's a shared object. We don't want the mDestroyed to be set on this. - } - - @Override public boolean isList() { - return false; - } - - @Override public boolean isString() { - return false; - } - - @Override public String toString() { - return "[NO ELEMENT]"; - } - - @Override - public boolean equalsForTest(ImapElement that) { - return super.equalsForTest(that); - } - }; - - private boolean mDestroyed = false; - - public abstract boolean isList(); - - public abstract boolean isString(); - - protected boolean isDestroyed() { - return mDestroyed; - } - - /** - * Clean up the resources used by the instance. - * It's for removing a temp file used by {@link ImapTempFileLiteral}. - */ - public void destroy() { - mDestroyed = true; - } - - /** - * Throws {@link RuntimeException} if it's already destroyed. - */ - protected final void checkNotDestroyed() { - if (mDestroyed) { - throw new RuntimeException("Already destroyed"); - } - } - - /** - * Return a string that represents this object; it's purely for the debug purpose. Don't - * mistake it for {@link ImapString#getString}. - * - * Abstract to force subclasses to implement it. - */ - @Override - public abstract String toString(); - - /** - * The equals implementation that is intended to be used only for unit testing. - * (Because it may be heavy and has a special sense of "equal" for testing.) - */ - public boolean equalsForTest(ImapElement that) { - if (that == null) { - return false; - } - return this.getClass() == that.getClass(); // Has to be the same class. - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapList.java b/src/com/android/phone/common/mail/store/imap/ImapList.java deleted file mode 100644 index 93adc4f..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapList.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 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.imap; - -import java.util.ArrayList; - -/** - * Class represents an IMAP list. - */ -public class ImapList extends ImapElement { - /** - * {@link ImapList} representing an empty list. - */ - public static final ImapList EMPTY = new ImapList() { - @Override public void destroy() { - // Don't call super.destroy(). - // It's a shared object. We don't want the mDestroyed to be set on this. - } - - @Override void add(ImapElement e) { - throw new RuntimeException(); - } - }; - - private ArrayList mList = new ArrayList(); - - /* package */ void add(ImapElement e) { - if (e == null) { - throw new RuntimeException("Can't add null"); - } - mList.add(e); - } - - @Override - public final boolean isString() { - return false; - } - - @Override - public final boolean isList() { - return true; - } - - public final int size() { - return mList.size(); - } - - public final boolean isEmpty() { - return size() == 0; - } - - /** - * Return true if the element at {@code index} exists, is string, and equals to {@code s}. - * (case insensitive) - */ - public final boolean is(int index, String s) { - return is(index, s, false); - } - - /** - * Same as {@link #is(int, String)}, but does the prefix match if {@code prefixMatch}. - */ - public final boolean is(int index, String s, boolean prefixMatch) { - if (!prefixMatch) { - return getStringOrEmpty(index).is(s); - } else { - return getStringOrEmpty(index).startsWith(s); - } - } - - /** - * Return the element at {@code index}. - * If {@code index} is out of range, returns {@link ImapElement#NONE}. - */ - public final ImapElement getElementOrNone(int index) { - return (index >= mList.size()) ? ImapElement.NONE : mList.get(index); - } - - /** - * Return the element at {@code index} if it's a list. - * If {@code index} is out of range or not a list, returns {@link ImapList#EMPTY}. - */ - public final ImapList getListOrEmpty(int index) { - ImapElement el = getElementOrNone(index); - return el.isList() ? (ImapList) el : EMPTY; - } - - /** - * Return the element at {@code index} if it's a string. - * If {@code index} is out of range or not a string, returns {@link ImapString#EMPTY}. - */ - public final ImapString getStringOrEmpty(int index) { - ImapElement el = getElementOrNone(index); - return el.isString() ? (ImapString) el : ImapString.EMPTY; - } - - /** - * Return an element keyed by {@code key}. Return null if not found. {@code key} has to be - * at an even index. - */ - /* package */ final ImapElement getKeyedElementOrNull(String key, boolean prefixMatch) { - for (int i = 1; i < size(); i += 2) { - if (is(i-1, key, prefixMatch)) { - return mList.get(i); - } - } - return null; - } - - /** - * Return an {@link ImapList} keyed by {@code key}. - * Return {@link ImapList#EMPTY} if not found. - */ - public final ImapList getKeyedListOrEmpty(String key) { - return getKeyedListOrEmpty(key, false); - } - - /** - * Return an {@link ImapList} keyed by {@code key}. - * Return {@link ImapList#EMPTY} if not found. - */ - public final ImapList getKeyedListOrEmpty(String key, boolean prefixMatch) { - ImapElement e = getKeyedElementOrNull(key, prefixMatch); - return (e != null) ? ((ImapList) e) : ImapList.EMPTY; - } - - /** - * Return an {@link ImapString} keyed by {@code key}. - * Return {@link ImapString#EMPTY} if not found. - */ - public final ImapString getKeyedStringOrEmpty(String key) { - return getKeyedStringOrEmpty(key, false); - } - - /** - * Return an {@link ImapString} keyed by {@code key}. - * Return {@link ImapString#EMPTY} if not found. - */ - public final ImapString getKeyedStringOrEmpty(String key, boolean prefixMatch) { - ImapElement e = getKeyedElementOrNull(key, prefixMatch); - return (e != null) ? ((ImapString) e) : ImapString.EMPTY; - } - - /** - * Return true if it contains {@code s}. - */ - public final boolean contains(String s) { - for (int i = 0; i < size(); i++) { - if (getStringOrEmpty(i).is(s)) { - return true; - } - } - return false; - } - - @Override - public void destroy() { - if (mList != null) { - for (ImapElement e : mList) { - e.destroy(); - } - mList = null; - } - super.destroy(); - } - - @Override - public String toString() { - return mList.toString(); - } - - /** - * Return the text representations of the contents concatenated with ",". - */ - public final String flatten() { - return flatten(new StringBuilder()).toString(); - } - - /** - * Returns text representations (i.e. getString()) of contents joined together with - * "," as the separator. - * - * Only used for building the capability string passed to vendor policies. - * - * We can't use toString(), because it's for debugging (meaning the format may change any time), - * and it won't expand literals. - */ - private final StringBuilder flatten(StringBuilder sb) { - sb.append('['); - for (int i = 0; i < mList.size(); i++) { - if (i > 0) { - sb.append(','); - } - final ImapElement e = getElementOrNone(i); - if (e.isList()) { - getListOrEmpty(i).flatten(sb); - } else if (e.isString()) { - sb.append(getStringOrEmpty(i).getString()); - } - } - sb.append(']'); - return sb; - } - - @Override - public boolean equalsForTest(ImapElement that) { - if (!super.equalsForTest(that)) { - return false; - } - ImapList thatList = (ImapList) that; - if (size() != thatList.size()) { - return false; - } - for (int i = 0; i < size(); i++) { - if (!mList.get(i).equalsForTest(thatList.getElementOrNone(i))) { - return false; - } - } - return true; - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapMemoryLiteral.java b/src/com/android/phone/common/mail/store/imap/ImapMemoryLiteral.java deleted file mode 100644 index aac66c2..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapMemoryLiteral.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2010 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.imap; - -import android.util.Log; - -import com.android.phone.common.mail.FixedLengthInputStream; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; - -/** - * Subclass of {@link ImapString} used for literals backed by an in-memory byte array. - */ -public class ImapMemoryLiteral extends ImapString { - private final String TAG = "ImapMemoryLiteral"; - private byte[] mData; - - /* package */ ImapMemoryLiteral(FixedLengthInputStream in) throws IOException { - // We could use ByteArrayOutputStream and IOUtils.copy, but it'd perform an unnecessary - // copy.... - mData = new byte[in.getLength()]; - int pos = 0; - while (pos < mData.length) { - int read = in.read(mData, pos, mData.length - pos); - if (read < 0) { - break; - } - pos += read; - } - if (pos != mData.length) { - Log.w(TAG, ""); - } - } - - @Override - public void destroy() { - mData = null; - super.destroy(); - } - - @Override - public String getString() { - try { - return new String(mData, "US-ASCII"); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Unsupported encoding: ", e); - } - return null; - } - - @Override - public InputStream getAsStream() { - return new ByteArrayInputStream(mData); - } - - @Override - public String toString() { - return String.format("{%d byte literal(memory)}", mData.length); - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapResponse.java b/src/com/android/phone/common/mail/store/imap/ImapResponse.java deleted file mode 100644 index 4891966..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapResponse.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.imap; - -/** - * Class represents an IMAP response. - */ -public class ImapResponse extends ImapList { - private final String mTag; - private final boolean mIsContinuationRequest; - - /* package */ ImapResponse(String tag, boolean isContinuationRequest) { - mTag = tag; - mIsContinuationRequest = isContinuationRequest; - } - - /* package */ static boolean isStatusResponse(String symbol) { - return ImapConstants.OK.equalsIgnoreCase(symbol) - || ImapConstants.NO.equalsIgnoreCase(symbol) - || ImapConstants.BAD.equalsIgnoreCase(symbol) - || ImapConstants.PREAUTH.equalsIgnoreCase(symbol) - || ImapConstants.BYE.equalsIgnoreCase(symbol); - } - - /** - * @return whether it's a tagged response. - */ - public boolean isTagged() { - return mTag != null; - } - - /** - * @return whether it's a continuation request. - */ - public boolean isContinuationRequest() { - return mIsContinuationRequest; - } - - public boolean isStatusResponse() { - return isStatusResponse(getStringOrEmpty(0).getString()); - } - - /** - * @return whether it's an OK response. - */ - public boolean isOk() { - return is(0, ImapConstants.OK); - } - - /** - * @return whether it's an BAD response. - */ - public boolean isBad() { - return is(0, ImapConstants.BAD); - } - - /** - * @return whether it's an NO response. - */ - public boolean isNo() { - return is(0, ImapConstants.NO); - } - - /** - * @return whether it's an {@code responseType} data response. (i.e. not tagged). - * @param index where {@code responseType} should appear. e.g. 1 for "FETCH" - * @param responseType e.g. "FETCH" - */ - public final boolean isDataResponse(int index, String responseType) { - return !isTagged() && getStringOrEmpty(index).is(responseType); - } - - /** - * @return Response code (RFC 3501 7.1) if it's a status response. - * - * e.g. "ALERT" for "* OK [ALERT] System shutdown in 10 minutes" - */ - public ImapString getResponseCodeOrEmpty() { - if (!isStatusResponse()) { - return ImapString.EMPTY; // Not a status response. - } - return getListOrEmpty(1).getStringOrEmpty(0); - } - - /** - * @return Alert message it it has ALERT response code. - * - * e.g. "System shutdown in 10 minutes" for "* OK [ALERT] System shutdown in 10 minutes" - */ - public ImapString getAlertTextOrEmpty() { - if (!getResponseCodeOrEmpty().is(ImapConstants.ALERT)) { - return ImapString.EMPTY; // Not an ALERT - } - // The 3rd element contains all the rest of line. - return getStringOrEmpty(2); - } - - /** - * @return Response text in a status response. - */ - public ImapString getStatusResponseTextOrEmpty() { - if (!isStatusResponse()) { - return ImapString.EMPTY; - } - return getStringOrEmpty(getElementOrNone(1).isList() ? 2 : 1); - } - - public ImapString getStatusOrEmpty() { - if (!isStatusResponse()) { - return ImapString.EMPTY; - } - return getStringOrEmpty(0); - } - - @Override - public String toString() { - String tag = mTag; - if (isContinuationRequest()) { - tag = "+"; - } - return "#" + tag + "# " + super.toString(); - } - - @Override - public boolean equalsForTest(ImapElement that) { - if (!super.equalsForTest(that)) { - return false; - } - final ImapResponse thatResponse = (ImapResponse) that; - if (mTag == null) { - if (thatResponse.mTag != null) { - return false; - } - } else { - if (!mTag.equals(thatResponse.mTag)) { - return false; - } - } - if (mIsContinuationRequest != thatResponse.mIsContinuationRequest) { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapResponseParser.java b/src/com/android/phone/common/mail/store/imap/ImapResponseParser.java deleted file mode 100644 index 1bf1565..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapResponseParser.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (C) 2010 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.imap; - -import android.text.TextUtils; -import android.util.Log; - -import com.android.phone.common.mail.FixedLengthInputStream; -import com.android.phone.common.mail.PeekableInputStream; -import com.android.phone.common.mail.MessagingException; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; - -/** - * IMAP response parser. - */ -public class ImapResponseParser { - private static final String TAG = "ImapResponseParser"; - - /** - * Literal larger than this will be stored in temp file. - */ - public static final int LITERAL_KEEP_IN_MEMORY_THRESHOLD = 2 * 1024 * 1024; - - /** Input stream */ - private final PeekableInputStream mIn; - - private final int mLiteralKeepInMemoryThreshold; - - /** StringBuilder used by readUntil() */ - private final StringBuilder mBufferReadUntil = new StringBuilder(); - - /** StringBuilder used by parseBareString() */ - private final StringBuilder mParseBareString = new StringBuilder(); - - /** - * We store all {@link ImapResponse} in it. {@link #destroyResponses()} must be called from - * time to time to destroy them and clear it. - */ - private final ArrayList mResponsesToDestroy = new ArrayList(); - - /** - * Exception thrown when we receive BYE. It derives from IOException, so it'll be treated - * in the same way EOF does. - */ - public static class ByeException extends IOException { - public static final String MESSAGE = "Received BYE"; - public ByeException() { - super(MESSAGE); - } - } - - /** - * Public constructor for normal use. - */ - public ImapResponseParser(InputStream in) { - this(in, LITERAL_KEEP_IN_MEMORY_THRESHOLD); - } - - /** - * Constructor for testing to override the literal size threshold. - */ - /* package for test */ ImapResponseParser(InputStream in, int literalKeepInMemoryThreshold) { - mIn = new PeekableInputStream(in); - mLiteralKeepInMemoryThreshold = literalKeepInMemoryThreshold; - } - - private static IOException newEOSException() { - final String message = "End of stream reached"; - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, message); - } - return new IOException(message); - } - - /** - * Peek next one byte. - * - * Throws IOException() if reaches EOF. As long as logical response lines end with \r\n, - * we shouldn't see EOF during parsing. - */ - private int peek() throws IOException { - final int next = mIn.peek(); - if (next == -1) { - throw newEOSException(); - } - return next; - } - - /** - * Read and return one byte from {@link #mIn}, and put it in {@link #mDiscourseLogger}. - * - * Throws IOException() if reaches EOF. As long as logical response lines end with \r\n, - * we shouldn't see EOF during parsing. - */ - private int readByte() throws IOException { - int next = mIn.read(); - if (next == -1) { - throw newEOSException(); - } - return next; - } - - /** - * Destroy all the {@link ImapResponse}s stored in the internal storage and clear it. - * - * @see #readResponse() - */ - public void destroyResponses() { - for (ImapResponse r : mResponsesToDestroy) { - r.destroy(); - } - mResponsesToDestroy.clear(); - } - - /** - * Reads the next response available on the stream and returns an - * {@link ImapResponse} object that represents it. - * - *

When this method successfully returns an {@link ImapResponse}, the {@link ImapResponse} - * is stored in the internal storage. When the {@link ImapResponse} is no longer used - * {@link #destroyResponses} should be called to destroy all the responses in the array. - * - * @return the parsed {@link ImapResponse} object. - * @exception ByeException when detects BYE. - */ - public ImapResponse readResponse() throws IOException, MessagingException { - ImapResponse response = null; - try { - response = parseResponse(); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "<<< " + response.toString()); - } - } catch (RuntimeException e) { - // Parser crash -- log network activities. - onParseError(e); - throw e; - } catch (IOException e) { - // Network error, or received an unexpected char. - onParseError(e); - throw e; - } - - // Handle this outside of try-catch. We don't have to dump protocol log when getting BYE. - if (response.is(0, ImapConstants.BYE)) { - Log.w(TAG, ByeException.MESSAGE); - response.destroy(); - throw new ByeException(); - } - mResponsesToDestroy.add(response); - return response; - } - - private void onParseError(Exception e) { - // Read a few more bytes, so that the log will contain some more context, even if the parser - // crashes in the middle of a response. - // This also makes sure the byte in question will be logged, no matter where it crashes. - // e.g. when parseAtom() peeks and finds at an unexpected char, it throws an exception - // before actually reading it. - // However, we don't want to read too much, because then it may get into an email message. - try { - for (int i = 0; i < 4; i++) { - int b = readByte(); - if (b == -1 || b == '\n') { - break; - } - } - } catch (IOException ignore) { - } - Log.w(TAG, "Exception detected: " + e.getMessage()); - } - - /** - * Read next byte from stream and throw it away. If the byte is different from {@code expected} - * throw {@link MessagingException}. - */ - /* package for test */ void expect(char expected) throws IOException { - final int next = readByte(); - if (expected != next) { - throw new IOException(String.format("Expected %04x (%c) but got %04x (%c)", - (int) expected, expected, next, (char) next)); - } - } - - /** - * Read bytes until we find {@code end}, and return all as string. - * The {@code end} will be read (rather than peeked) and won't be included in the result. - */ - /* package for test */ String readUntil(char end) throws IOException { - mBufferReadUntil.setLength(0); - for (;;) { - final int ch = readByte(); - if (ch != end) { - mBufferReadUntil.append((char) ch); - } else { - return mBufferReadUntil.toString(); - } - } - } - - /** - * Read all bytes until \r\n. - */ - /* package */ String readUntilEol() throws IOException { - String ret = readUntil('\r'); - expect('\n'); // TODO Should this really be error? - return ret; - } - - /** - * Parse and return the response line. - */ - private ImapResponse parseResponse() throws IOException, MessagingException { - // We need to destroy the response if we get an exception. - // So, we first store the response that's being built in responseToDestroy, until it's - // completely built, at which point we copy it into responseToReturn and null out - // responseToDestroyt. - // If responseToDestroy is not null in finally, we destroy it because that means - // we got an exception somewhere. - ImapResponse responseToDestroy = null; - final ImapResponse responseToReturn; - - try { - final int ch = peek(); - if (ch == '+') { // Continuation request - readByte(); // skip + - expect(' '); - responseToDestroy = new ImapResponse(null, true); - - // If it's continuation request, we don't really care what's in it. - responseToDestroy.add(new ImapSimpleString(readUntilEol())); - - // Response has successfully been built. Let's return it. - responseToReturn = responseToDestroy; - responseToDestroy = null; - } else { - // Status response or response data - final String tag; - if (ch == '*') { - tag = null; - readByte(); // skip * - expect(' '); - } else { - tag = readUntil(' '); - } - responseToDestroy = new ImapResponse(tag, false); - - final ImapString firstString = parseBareString(); - responseToDestroy.add(firstString); - - // parseBareString won't eat a space after the string, so we need to skip it, - // if exists. - // If the next char is not ' ', it should be EOL. - if (peek() == ' ') { - readByte(); // skip ' ' - - if (responseToDestroy.isStatusResponse()) { // It's a status response - - // Is there a response code? - final int next = peek(); - if (next == '[') { - responseToDestroy.add(parseList('[', ']')); - if (peek() == ' ') { // Skip following space - readByte(); - } - } - - String rest = readUntilEol(); - if (!TextUtils.isEmpty(rest)) { - // The rest is free-form text. - responseToDestroy.add(new ImapSimpleString(rest)); - } - } else { // It's a response data. - parseElements(responseToDestroy, '\0'); - } - } else { - expect('\r'); - expect('\n'); - } - - // Response has successfully been built. Let's return it. - responseToReturn = responseToDestroy; - responseToDestroy = null; - } - } finally { - if (responseToDestroy != null) { - // We get an exception. - responseToDestroy.destroy(); - } - } - - return responseToReturn; - } - - private ImapElement parseElement() throws IOException, MessagingException { - final int next = peek(); - switch (next) { - case '(': - return parseList('(', ')'); - case '[': - return parseList('[', ']'); - case '"': - readByte(); // Skip " - return new ImapSimpleString(readUntil('"')); - case '{': - return parseLiteral(); - case '\r': // CR - readByte(); // Consume \r - expect('\n'); // Should be followed by LF. - return null; - case '\n': // LF // There shouldn't be a bare LF, but just in case. - readByte(); // Consume \n - return null; - default: - return parseBareString(); - } - } - - /** - * Parses an atom. - * - * Special case: If an atom contains '[', everything until the next ']' will be considered - * a part of the atom. - * (e.g. "BODY[HEADER.FIELDS ("DATE" ...)]" will become a single ImapString) - * - * If the value is "NIL", returns an empty string. - */ - private ImapString parseBareString() throws IOException, MessagingException { - mParseBareString.setLength(0); - for (;;) { - final int ch = peek(); - - // TODO Can we clean this up? (This condition is from the old parser.) - if (ch == '(' || ch == ')' || ch == '{' || ch == ' ' || - // ']' is not part of atom (it's in resp-specials) - ch == ']' || - // docs claim that flags are \ atom but atom isn't supposed to - // contain - // * and some flags contain * - // ch == '%' || ch == '*' || - ch == '%' || - // TODO probably should not allow \ and should recognize - // it as a flag instead - // ch == '"' || ch == '\' || - ch == '"' || (0x00 <= ch && ch <= 0x1f) || ch == 0x7f) { - if (mParseBareString.length() == 0) { - throw new MessagingException("Expected string, none found."); - } - String s = mParseBareString.toString(); - - // NIL will be always converted into the empty string. - if (ImapConstants.NIL.equalsIgnoreCase(s)) { - return ImapString.EMPTY; - } - return new ImapSimpleString(s); - } else if (ch == '[') { - // Eat all until next ']' - mParseBareString.append((char) readByte()); - mParseBareString.append(readUntil(']')); - mParseBareString.append(']'); // readUntil won't include the end char. - } else { - mParseBareString.append((char) readByte()); - } - } - } - - private void parseElements(ImapList list, char end) - throws IOException, MessagingException { - for (;;) { - for (;;) { - final int next = peek(); - if (next == end) { - return; - } - if (next != ' ') { - break; - } - // Skip space - readByte(); - } - final ImapElement el = parseElement(); - if (el == null) { // EOL - return; - } - list.add(el); - } - } - - private ImapList parseList(char opening, char closing) - throws IOException, MessagingException { - expect(opening); - final ImapList list = new ImapList(); - parseElements(list, closing); - expect(closing); - return list; - } - - private ImapString parseLiteral() throws IOException, MessagingException { - expect('{'); - final int size; - try { - size = Integer.parseInt(readUntil('}')); - } catch (NumberFormatException nfe) { - throw new MessagingException("Invalid length in literal"); - } - if (size < 0) { - throw new MessagingException("Invalid negative length in literal"); - } - expect('\r'); - expect('\n'); - FixedLengthInputStream in = new FixedLengthInputStream(mIn, size); - if (size > mLiteralKeepInMemoryThreshold) { - return new ImapTempFileLiteral(in); - } else { - return new ImapMemoryLiteral(in); - } - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapSimpleString.java b/src/com/android/phone/common/mail/store/imap/ImapSimpleString.java deleted file mode 100644 index 3d5263b..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapSimpleString.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.imap; - -import android.util.Log; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; - -/** - * Subclass of {@link ImapString} used for non literals. - */ -public class ImapSimpleString extends ImapString { - private final String TAG = "ImapSimpleString"; - private String mString; - - /* package */ ImapSimpleString(String string) { - mString = (string != null) ? string : ""; - } - - @Override - public void destroy() { - mString = null; - super.destroy(); - } - - @Override - public String getString() { - return mString; - } - - @Override - public InputStream getAsStream() { - try { - return new ByteArrayInputStream(mString.getBytes("US-ASCII")); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Unsupported encoding: ", e); - } - return null; - } - - @Override - public String toString() { - // Purposefully not return just mString, in order to prevent using it instead of getString. - return "\"" + mString + "\""; - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapString.java b/src/com/android/phone/common/mail/store/imap/ImapString.java deleted file mode 100644 index f38a993..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapString.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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.imap; - -import android.util.Log; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -/** - * Class represents an IMAP "element" that is not a list. - * - * An atom, quoted string, literal, are all represented by this. Values like OK, STATUS are too. - * Also, this class class may contain more arbitrary value like "BODY[HEADER.FIELDS ("DATE")]". - * See {@link ImapResponseParser}. - */ -public abstract class ImapString extends ImapElement { - private static final byte[] EMPTY_BYTES = new byte[0]; - - public static final ImapString EMPTY = new ImapString() { - @Override public void destroy() { - // Don't call super.destroy(). - // It's a shared object. We don't want the mDestroyed to be set on this. - } - - @Override public String getString() { - return ""; - } - - @Override public InputStream getAsStream() { - return new ByteArrayInputStream(EMPTY_BYTES); - } - - @Override public String toString() { - return ""; - } - }; - - // This is used only for parsing IMAP's FETCH ENVELOPE command, in which - // en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be - // handled by Locale.US - private final static SimpleDateFormat DATE_TIME_FORMAT = - new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US); - - private boolean mIsInteger; - private int mParsedInteger; - private Date mParsedDate; - - @Override - public final boolean isList() { - return false; - } - - @Override - public final boolean isString() { - return true; - } - - /** - * @return true if and only if the length of the string is larger than 0. - * - * Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser - * #parseBareString}. - * On the other hand, a quoted/literal string with value NIL (i.e. "NIL" and {3}\r\nNIL) is - * treated literally. - */ - public final boolean isEmpty() { - return getString().length() == 0; - } - - public abstract String getString(); - - public abstract InputStream getAsStream(); - - /** - * @return whether it can be parsed as a number. - */ - public final boolean isNumber() { - if (mIsInteger) { - return true; - } - try { - mParsedInteger = Integer.parseInt(getString()); - mIsInteger = true; - return true; - } catch (NumberFormatException e) { - return false; - } - } - - /** - * @return value parsed as a number. - */ - public final int getNumberOrZero() { - if (!isNumber()) { - return 0; - } - return mParsedInteger; - } - - /** - * @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}. - */ - public final boolean isDate() { - if (mParsedDate != null) { - return true; - } - if (isEmpty()) { - return false; - } - try { - mParsedDate = DATE_TIME_FORMAT.parse(getString()); - return true; - } catch (ParseException e) { - Log.w("ImapString", getString() + " can't be parsed as a date."); - return false; - } - } - - /** - * @return value it can be parsed as a {@link Date}, or null otherwise. - */ - public final Date getDateOrNull() { - if (!isDate()) { - return null; - } - return mParsedDate; - } - - /** - * @return whether the value case-insensitively equals to {@code s}. - */ - public final boolean is(String s) { - if (s == null) { - return false; - } - return getString().equalsIgnoreCase(s); - } - - - /** - * @return whether the value case-insensitively starts with {@code s}. - */ - public final boolean startsWith(String prefix) { - if (prefix == null) { - return false; - } - final String me = this.getString(); - if (me.length() < prefix.length()) { - return false; - } - return me.substring(0, prefix.length()).equalsIgnoreCase(prefix); - } - - // To force subclasses to implement it. - @Override - public abstract String toString(); - - @Override - public final boolean equalsForTest(ImapElement that) { - if (!super.equalsForTest(that)) { - return false; - } - ImapString thatString = (ImapString) that; - return getString().equals(thatString.getString()); - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapTempFileLiteral.java b/src/com/android/phone/common/mail/store/imap/ImapTempFileLiteral.java deleted file mode 100644 index 2b1113e..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapTempFileLiteral.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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.imap; - -import android.content.Context; -import android.util.Log; - -import com.android.phone.common.mail.FixedLengthInputStream; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Subclass of {@link ImapString} used for literals backed by a temp file. - */ -public class ImapTempFileLiteral extends ImapString { - private final String TAG = "ImapTempFileLiteral"; - - /* package for test */ final File mFile; - - /** Size is purely for toString() */ - private final int mSize; - - /* package */ ImapTempFileLiteral(FixedLengthInputStream stream) throws IOException { - mSize = stream.getLength(); - mFile = File.createTempFile("imap", ".tmp", TempDirectory.getTempDirectory()); - - // Unfortunately, we can't really use deleteOnExit(), because temp filenames are random - // so it'd simply cause a memory leak. - // deleteOnExit() simply adds filenames to a static list and the list will never shrink. - // mFile.deleteOnExit(); - OutputStream out = new FileOutputStream(mFile); - StreamUtils.copy(stream, out); - out.close(); - } - - /** - * Copies utility methods for working with byte arrays and I/O streams from guava library. - */ - public static class StreamUtils { - private static final int BUF_SIZE = 0x1000; // 4K - /** - * Copies all bytes from the input stream to the output stream. - * Does not close or flush either stream. - * - * @param from the input stream to read from - * @param to the output stream to write to - * @return the number of bytes copied - * @throws IOException if an I/O error occurs - */ - public static long copy(InputStream from, OutputStream to) throws IOException { - checkNotNull(from); - checkNotNull(to); - byte[] buf = new byte[BUF_SIZE]; - long total = 0; - while (true) { - int r = from.read(buf); - if (r == -1) { - break; - } - to.write(buf, 0, r); - total += r; - } - return total; - } - - /** - * Reads all bytes from an input stream into a byte array. - * Does not close the stream. - * - * @param in the input stream to read from - * @return a byte array containing all the bytes from the stream - * @throws IOException if an I/O error occurs - */ - public static byte[] toByteArray(InputStream in) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - copy(in, out); - return out.toByteArray(); - } - - /** - * Ensures that an object reference passed as a parameter to the calling method is not null. - * - * @param reference an object reference - * @return the non-null reference that was validated - * @throws NullPointerException if {@code reference} is null - */ - public static T checkNotNull(T reference) { - if (reference == null) { - throw new NullPointerException(); - } - return reference; - } - } - - /** - * Make sure we delete the temp file. - * - * We should always be calling {@link ImapResponse#destroy()}, but it's here as a last resort. - */ - @Override - protected void finalize() throws Throwable { - try { - destroy(); - } finally { - super.finalize(); - } - } - - @Override - public InputStream getAsStream() { - checkNotDestroyed(); - try { - return new FileInputStream(mFile); - } catch (FileNotFoundException e) { - // It's probably possible if we're low on storage and the system clears the cache dir. - Log.w(TAG, "ImapTempFileLiteral: Temp file not found"); - - // Return 0 byte stream as a dummy... - return new ByteArrayInputStream(new byte[0]); - } - } - - @Override - public String getString() { - checkNotDestroyed(); - try { - byte[] bytes = StreamUtils.toByteArray(getAsStream()); - // Prevent crash from OOM; we've seen this, but only rarely and not reproducibly - if (bytes.length > ImapResponseParser.LITERAL_KEEP_IN_MEMORY_THRESHOLD) { - throw new IOException(); - } - return new String(bytes, "US-ASCII"); - } catch (IOException e) { - Log.w(TAG, "ImapTempFileLiteral: Error while reading temp file", e); - return ""; - } - } - - @Override - public void destroy() { - try { - if (!isDestroyed() && mFile.exists()) { - mFile.delete(); - } - } catch (RuntimeException re) { - // Just log and ignore. - Log.w(TAG, "Failed to remove temp file: " + re.getMessage()); - } - super.destroy(); - } - - @Override - public String toString() { - return String.format("{%d byte literal(file)}", mSize); - } - - public boolean tempFileExistsForTest() { - return mFile.exists(); - } - - /** - * TempDirectory caches the directory used for caching file. It is set up during application - * initialization. - */ - public static class TempDirectory { - private static File sTempDirectory = null; - - public static void setTempDirectory(Context context) { - sTempDirectory = context.getCacheDir(); - } - - public static File getTempDirectory() { - if (sTempDirectory == null) { - throw new RuntimeException( - "TempDirectory not set. " + - "If in a unit test, call Email.setTempDirectory(context) in setUp()."); - } - return sTempDirectory; - } - } -} \ No newline at end of file diff --git a/src/com/android/phone/common/mail/store/imap/ImapUtility.java b/src/com/android/phone/common/mail/store/imap/ImapUtility.java deleted file mode 100644 index 28c8b6f..0000000 --- a/src/com/android/phone/common/mail/store/imap/ImapUtility.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.imap; - -/** - * Utility methods for use with IMAP. - */ -public class ImapUtility { - /** - * Apply quoting rules per IMAP RFC, - * quoted = DQUOTE *QUOTED-CHAR DQUOTE - * QUOTED-CHAR = / "\" quoted-specials - * quoted-specials = DQUOTE / "\" - * - * This is used primarily for IMAP login, but might be useful elsewhere. - * - * NOTE: Not very efficient - you may wish to preflight this, or perhaps it should check - * for trouble chars before calling the replace functions. - * - * @param s The string to be quoted. - * @return A copy of the string, having undergone quoting as described above - */ - public static String imapQuoted(String s) { - - // First, quote any backslashes by replacing \ with \\ - // regex Pattern: \\ (Java string const = \\\\) - // Substitute: \\\\ (Java string const = \\\\\\\\) - String result = s.replaceAll("\\\\", "\\\\\\\\"); - - // Then, quote any double-quotes by replacing " with \" - // regex Pattern: " (Java string const = \") - // Substitute: \\" (Java string const = \\\\\") - result = result.replaceAll("\"", "\\\\\""); - - // return string with quotes around it - return "\"" + result + "\""; - } -} \ No newline at end of file -- cgit v1.2.3