2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008 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.email.mail.store;
|
|
|
|
|
|
|
|
import com.android.email.Email;
|
2010-02-01 23:53:46 +00:00
|
|
|
import com.android.email.Preferences;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.Utility;
|
2010-02-01 23:53:46 +00:00
|
|
|
import com.android.email.VendorPolicyLoader;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.AuthenticationFailedException;
|
2010-08-18 15:50:45 +00:00
|
|
|
import com.android.email.mail.Body;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.CertificateValidationException;
|
|
|
|
import com.android.email.mail.FetchProfile;
|
|
|
|
import com.android.email.mail.Flag;
|
|
|
|
import com.android.email.mail.Folder;
|
|
|
|
import com.android.email.mail.Message;
|
|
|
|
import com.android.email.mail.MessagingException;
|
|
|
|
import com.android.email.mail.Part;
|
|
|
|
import com.android.email.mail.Store;
|
|
|
|
import com.android.email.mail.Transport;
|
2010-08-18 15:50:45 +00:00
|
|
|
import com.android.email.mail.internet.BinaryTempFileBody;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.internet.MimeBodyPart;
|
|
|
|
import com.android.email.mail.internet.MimeHeader;
|
|
|
|
import com.android.email.mail.internet.MimeMessage;
|
|
|
|
import com.android.email.mail.internet.MimeMultipart;
|
|
|
|
import com.android.email.mail.internet.MimeUtility;
|
2010-05-20 00:23:23 +00:00
|
|
|
import com.android.email.mail.store.imap.ImapConstants;
|
|
|
|
import com.android.email.mail.store.imap.ImapElement;
|
|
|
|
import com.android.email.mail.store.imap.ImapList;
|
|
|
|
import com.android.email.mail.store.imap.ImapResponse;
|
|
|
|
import com.android.email.mail.store.imap.ImapResponseParser;
|
|
|
|
import com.android.email.mail.store.imap.ImapString;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.transport.CountingOutputStream;
|
2010-03-08 19:59:14 +00:00
|
|
|
import com.android.email.mail.transport.DiscourseLogger;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.transport.EOLConvertingOutputStream;
|
|
|
|
import com.android.email.mail.transport.MailTransport;
|
2010-06-02 20:25:03 +00:00
|
|
|
import com.android.email.service.EmailServiceProxy;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.beetstra.jutf7.CharsetProvider;
|
|
|
|
|
2009-03-27 00:05:25 +00:00
|
|
|
import android.content.Context;
|
2010-01-26 02:40:45 +00:00
|
|
|
import android.os.Build;
|
2010-06-02 20:25:03 +00:00
|
|
|
import android.os.Bundle;
|
2010-02-01 23:53:46 +00:00
|
|
|
import android.telephony.TelephonyManager;
|
2010-05-20 00:23:23 +00:00
|
|
|
import android.text.TextUtils;
|
2010-03-30 22:29:51 +00:00
|
|
|
import android.util.Base64;
|
2009-03-04 03:32:22 +00:00
|
|
|
import android.util.Config;
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
2010-08-18 15:50:45 +00:00
|
|
|
import java.io.OutputStream;
|
2009-03-04 03:32:22 +00:00
|
|
|
import java.net.URI;
|
|
|
|
import java.net.URISyntaxException;
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.charset.Charset;
|
2010-02-01 23:53:46 +00:00
|
|
|
import java.security.MessageDigest;
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
2009-03-04 03:32:22 +00:00
|
|
|
import java.util.ArrayList;
|
2010-05-19 00:22:28 +00:00
|
|
|
import java.util.Collection;
|
2009-03-04 03:32:22 +00:00
|
|
|
import java.util.Date;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.LinkedHashSet;
|
|
|
|
import java.util.List;
|
2010-05-19 00:22:28 +00:00
|
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
2010-02-01 23:53:46 +00:00
|
|
|
import java.util.regex.Pattern;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
import javax.net.ssl.SSLException;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* <pre>
|
|
|
|
* TODO Need to start keeping track of UIDVALIDITY
|
|
|
|
* TODO Need a default response handler for things like folder updates
|
|
|
|
* TODO In fetch(), if we need a ImapMessage and were given
|
2010-05-20 21:47:59 +00:00
|
|
|
* TODO Collect ALERT messages and show them to users.
|
2009-03-04 03:32:22 +00:00
|
|
|
* something else we can try to do a pre-fetch first.
|
|
|
|
*
|
|
|
|
* ftp://ftp.isi.edu/in-notes/rfc2683.txt When a client asks for
|
|
|
|
* certain information in a FETCH command, the server may return the requested
|
|
|
|
* information in any order, not necessarily in the order that it was requested.
|
|
|
|
* Further, the server may return the information in separate FETCH responses
|
|
|
|
* and may also return information that was not explicitly requested (to reflect
|
|
|
|
* to the client changes in the state of the subject message).
|
|
|
|
* </pre>
|
|
|
|
*/
|
|
|
|
public class ImapStore extends Store {
|
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
// Always check in FALSE
|
|
|
|
private static final boolean DEBUG_FORCE_SEND_ID = false;
|
|
|
|
|
2010-08-18 15:50:45 +00:00
|
|
|
private static final int COPY_BUFFER_SIZE = 16*1024;
|
|
|
|
|
2009-08-26 05:45:11 +00:00
|
|
|
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN, Flag.FLAGGED };
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-10 21:20:16 +00:00
|
|
|
private final Context mContext;
|
2009-03-04 03:32:22 +00:00
|
|
|
private Transport mRootTransport;
|
|
|
|
private String mUsername;
|
|
|
|
private String mPassword;
|
|
|
|
private String mLoginPhrase;
|
|
|
|
private String mPathPrefix;
|
2010-01-26 02:40:45 +00:00
|
|
|
private String mIdPhrase = null;
|
|
|
|
private static String sImapId = null;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-19 00:22:28 +00:00
|
|
|
private final ConcurrentLinkedQueue<ImapConnection> mConnectionPool =
|
|
|
|
new ConcurrentLinkedQueue<ImapConnection>();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Charset used for converting folder names to and from UTF-7 as defined by RFC 3501.
|
|
|
|
*/
|
2010-05-18 20:41:14 +00:00
|
|
|
private static final Charset MODIFIED_UTF_7_CHARSET =
|
|
|
|
new CharsetProvider().charsetForName("X-RFC-3501");
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Cache of ImapFolder objects. ImapFolders are attached to a given folder on the server
|
|
|
|
* and as long as their associated connection remains open they are reusable between
|
|
|
|
* requests. This cache lets us make sure we always reuse, if possible, for a given
|
|
|
|
* folder name.
|
|
|
|
*/
|
|
|
|
private HashMap<String, ImapFolder> mFolderCache = new HashMap<String, ImapFolder>();
|
|
|
|
|
2010-05-19 00:22:28 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
|
2009-03-27 00:05:25 +00:00
|
|
|
/**
|
|
|
|
* Static named constructor.
|
|
|
|
*/
|
2009-04-14 03:07:56 +00:00
|
|
|
public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks)
|
|
|
|
throws MessagingException {
|
2010-01-26 02:40:45 +00:00
|
|
|
return new ImapStore(context, uri);
|
2009-03-27 00:05:25 +00:00
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Allowed formats for the Uri:
|
2009-09-29 22:28:43 +00:00
|
|
|
* imap://user:password@server:port
|
|
|
|
* imap+tls+://user:password@server:port
|
|
|
|
* imap+tls+trustallcerts://user:password@server:port
|
|
|
|
* imap+ssl+://user:password@server:port
|
|
|
|
* imap+ssl+trustallcerts://user:password@server:port
|
2009-03-04 03:32:22 +00:00
|
|
|
*
|
|
|
|
* @param uriString the Uri containing information to configure this store
|
|
|
|
*/
|
2010-01-26 02:40:45 +00:00
|
|
|
private ImapStore(Context context, String uriString) throws MessagingException {
|
2010-02-26 06:48:11 +00:00
|
|
|
mContext = context;
|
2009-03-04 03:32:22 +00:00
|
|
|
URI uri;
|
|
|
|
try {
|
|
|
|
uri = new URI(uriString);
|
|
|
|
} catch (URISyntaxException use) {
|
|
|
|
throw new MessagingException("Invalid ImapStore URI", use);
|
|
|
|
}
|
|
|
|
|
|
|
|
String scheme = uri.getScheme();
|
2009-09-29 22:28:43 +00:00
|
|
|
if (scheme == null || !scheme.startsWith(STORE_SCHEME_IMAP)) {
|
|
|
|
throw new MessagingException("Unsupported protocol");
|
|
|
|
}
|
|
|
|
// defaults, which can be changed by security modifiers
|
2009-03-04 03:32:22 +00:00
|
|
|
int connectionSecurity = Transport.CONNECTION_SECURITY_NONE;
|
2009-09-29 22:28:43 +00:00
|
|
|
int defaultPort = 143;
|
|
|
|
// check for security modifiers and apply changes
|
|
|
|
if (scheme.contains("+ssl")) {
|
|
|
|
connectionSecurity = Transport.CONNECTION_SECURITY_SSL;
|
2009-03-04 03:32:22 +00:00
|
|
|
defaultPort = 993;
|
2009-09-29 22:28:43 +00:00
|
|
|
} else if (scheme.contains("+tls")) {
|
|
|
|
connectionSecurity = Transport.CONNECTION_SECURITY_TLS;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-09-29 22:28:43 +00:00
|
|
|
boolean trustCertificates = scheme.contains(STORE_SECURITY_TRUST_CERTIFICATES);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
mRootTransport = new MailTransport("IMAP");
|
|
|
|
mRootTransport.setUri(uri, defaultPort);
|
2009-09-29 22:28:43 +00:00
|
|
|
mRootTransport.setSecurity(connectionSecurity, trustCertificates);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
String[] userInfoParts = mRootTransport.getUserInfoParts();
|
|
|
|
if (userInfoParts != null) {
|
|
|
|
mUsername = userInfoParts[0];
|
|
|
|
if (userInfoParts.length > 1) {
|
|
|
|
mPassword = userInfoParts[1];
|
2010-02-08 21:04:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
// build the LOGIN string once (instead of over-and-over again.)
|
|
|
|
// apply the quoting here around the built-up password
|
2010-05-29 00:34:47 +00:00
|
|
|
mLoginPhrase = ImapConstants.LOGIN + " " + mUsername + " "
|
|
|
|
+ Utility.imapQuoted(mPassword);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((uri.getPath() != null) && (uri.getPath().length() > 0)) {
|
|
|
|
mPathPrefix = uri.getPath().substring(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-19 00:22:28 +00:00
|
|
|
/* package */ Collection<ImapConnection> getConnectionPoolForTest() {
|
|
|
|
return mConnectionPool;
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
2010-02-08 21:04:03 +00:00
|
|
|
* For testing only. Injects a different root transport (it will be copied using
|
|
|
|
* newInstanceWithConfiguration() each time IMAP sets up a new channel). The transport
|
2009-03-04 03:32:22 +00:00
|
|
|
* should already be set up and ready to use. Do not use for real code.
|
|
|
|
* @param testTransport The Transport to inject and use for all future communication.
|
|
|
|
*/
|
|
|
|
/* package */ void setTransport(Transport testTransport) {
|
|
|
|
mRootTransport = testTransport;
|
|
|
|
}
|
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
/**
|
|
|
|
* Return, or create and return, an string suitable for use in an IMAP ID message.
|
|
|
|
* This is constructed similarly to the way the browser sets up its user-agent strings.
|
|
|
|
* See RFC 2971 for more details. The output of this command will be a series of key-value
|
|
|
|
* pairs delimited by spaces (there is no point in returning a structured result because
|
|
|
|
* this will be sent as-is to the IMAP server). No tokens, parenthesis or "ID" are included,
|
|
|
|
* because some connections may append additional values.
|
|
|
|
*
|
|
|
|
* The following IMAP ID keys may be included:
|
2010-02-01 23:53:46 +00:00
|
|
|
* name Android package name of the program
|
|
|
|
* os "android"
|
|
|
|
* os-version "version; model; build-id"
|
|
|
|
* vendor Vendor of the client/server
|
|
|
|
* x-android-device-model Model (only revealed if release build)
|
|
|
|
* x-android-net-operator Mobile network operator (if known)
|
|
|
|
* AGUID A device+account UID
|
2010-01-26 02:40:45 +00:00
|
|
|
*
|
2010-02-01 23:53:46 +00:00
|
|
|
* In addition, a vendor policy .apk can append key/value pairs.
|
|
|
|
*
|
|
|
|
* @param userName the username of the account
|
|
|
|
* @param host the host (server) of the account
|
2010-02-26 06:48:11 +00:00
|
|
|
* @param capability the capabilities string from the server
|
2010-02-08 21:04:03 +00:00
|
|
|
* @return a String for use in an IMAP ID message.
|
2010-01-26 02:40:45 +00:00
|
|
|
*/
|
2010-06-14 16:55:41 +00:00
|
|
|
/* package */ static String getImapId(Context context, String userName, String host,
|
|
|
|
ImapResponse capabilityResponse) {
|
2010-02-01 23:53:46 +00:00
|
|
|
// The first section is global to all IMAP connections, and generates the fixed
|
|
|
|
// values in any IMAP ID message
|
|
|
|
synchronized (ImapStore.class) {
|
2010-01-26 02:40:45 +00:00
|
|
|
if (sImapId == null) {
|
2010-02-20 00:32:16 +00:00
|
|
|
TelephonyManager tm =
|
|
|
|
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
|
|
|
String networkOperator = tm.getNetworkOperatorName();
|
2010-02-01 23:53:46 +00:00
|
|
|
if (networkOperator == null) networkOperator = "";
|
2010-01-26 02:40:45 +00:00
|
|
|
|
2010-02-01 23:53:46 +00:00
|
|
|
sImapId = makeCommonImapId(context.getPackageName(), Build.VERSION.RELEASE,
|
|
|
|
Build.VERSION.CODENAME, Build.MODEL, Build.ID, Build.MANUFACTURER,
|
|
|
|
networkOperator);
|
|
|
|
}
|
|
|
|
}
|
2010-01-26 02:40:45 +00:00
|
|
|
|
2010-02-01 23:53:46 +00:00
|
|
|
// This section is per Store, and adds in a dynamic elements like UID's.
|
|
|
|
// We don't cache the result of this work, because the caller does anyway.
|
|
|
|
StringBuilder id = new StringBuilder(sImapId);
|
|
|
|
|
|
|
|
// Optionally add any vendor-supplied id keys
|
2010-06-14 16:55:41 +00:00
|
|
|
String vendorId = VendorPolicyLoader.getInstance(context).getImapIdValues(userName, host,
|
|
|
|
capabilityResponse.flatten());
|
2010-02-01 23:53:46 +00:00
|
|
|
if (vendorId != null) {
|
|
|
|
id.append(' ');
|
|
|
|
id.append(vendorId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a UID that mixes a "stable" device UID with the email address
|
|
|
|
try {
|
|
|
|
String devUID = Preferences.getPreferences(context).getDeviceUID();
|
|
|
|
MessageDigest messageDigest;
|
|
|
|
messageDigest = MessageDigest.getInstance("SHA-1");
|
|
|
|
messageDigest.update(userName.getBytes());
|
|
|
|
messageDigest.update(devUID.getBytes());
|
|
|
|
byte[] uid = messageDigest.digest();
|
2010-02-08 21:04:03 +00:00
|
|
|
String hexUid = Base64.encodeToString(uid, Base64.NO_WRAP);
|
2010-02-01 23:53:46 +00:00
|
|
|
id.append(" \"AGUID\" \"");
|
|
|
|
id.append(hexUid);
|
|
|
|
id.append('\"');
|
|
|
|
} catch (NoSuchAlgorithmException e) {
|
|
|
|
Log.d(Email.LOG_TAG, "couldn't obtain SHA-1 hash for device UID");
|
|
|
|
}
|
|
|
|
return id.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function that actually builds the static part of the IMAP ID string. This is
|
|
|
|
* separated from getImapId for testability. There is no escaping or encoding in IMAP ID so
|
|
|
|
* any rogue chars must be filtered here.
|
|
|
|
*
|
|
|
|
* @param packageName context.getPackageName()
|
|
|
|
* @param version Build.VERSION.RELEASE
|
|
|
|
* @param codeName Build.VERSION.CODENAME
|
|
|
|
* @param model Build.MODEL
|
|
|
|
* @param id Build.ID
|
|
|
|
* @param vendor Build.MANUFACTURER
|
|
|
|
* @param networkOperator TelephonyManager.getNetworkOperatorName()
|
|
|
|
* @return the static (never changes) portion of the IMAP ID
|
|
|
|
*/
|
2010-05-20 21:47:59 +00:00
|
|
|
/* package */ static String makeCommonImapId(String packageName, String version,
|
2010-02-01 23:53:46 +00:00
|
|
|
String codeName, String model, String id, String vendor, String networkOperator) {
|
|
|
|
|
|
|
|
// Before building up IMAP ID string, pre-filter the input strings for "legal" chars
|
|
|
|
// This is using a fairly arbitrary char set intended to pass through most reasonable
|
|
|
|
// version, model, and vendor strings: a-z A-Z 0-9 - _ + = ; : . , / <space>
|
|
|
|
// The most important thing is *not* to pass parens, quotes, or CRLF, which would break
|
|
|
|
// the format of the IMAP ID list.
|
|
|
|
Pattern p = Pattern.compile("[^a-zA-Z0-9-_\\+=;:\\.,/ ]");
|
|
|
|
packageName = p.matcher(packageName).replaceAll("");
|
|
|
|
version = p.matcher(version).replaceAll("");
|
|
|
|
codeName = p.matcher(codeName).replaceAll("");
|
|
|
|
model = p.matcher(model).replaceAll("");
|
|
|
|
id = p.matcher(id).replaceAll("");
|
|
|
|
vendor = p.matcher(vendor).replaceAll("");
|
|
|
|
networkOperator = p.matcher(networkOperator).replaceAll("");
|
|
|
|
|
|
|
|
// "name" "com.android.email"
|
|
|
|
StringBuffer sb = new StringBuffer("\"name\" \"");
|
|
|
|
sb.append(packageName);
|
|
|
|
sb.append("\"");
|
|
|
|
|
|
|
|
// "os" "android"
|
|
|
|
sb.append(" \"os\" \"android\"");
|
|
|
|
|
|
|
|
// "os-version" "version; build-id"
|
|
|
|
sb.append(" \"os-version\" \"");
|
|
|
|
if (version.length() > 0) {
|
|
|
|
sb.append(version);
|
|
|
|
} else {
|
|
|
|
// default to "1.0"
|
|
|
|
sb.append("1.0");
|
|
|
|
}
|
|
|
|
// add the build ID or build #
|
|
|
|
if (id.length() > 0) {
|
|
|
|
sb.append("; ");
|
|
|
|
sb.append(id);
|
|
|
|
}
|
|
|
|
sb.append("\"");
|
|
|
|
|
|
|
|
// "vendor" "the vendor"
|
|
|
|
if (vendor.length() > 0) {
|
|
|
|
sb.append(" \"vendor\" \"");
|
|
|
|
sb.append(vendor);
|
|
|
|
sb.append("\"");
|
|
|
|
}
|
|
|
|
|
|
|
|
// "x-android-device-model" the device model (on release builds only)
|
|
|
|
if ("REL".equals(codeName)) {
|
|
|
|
if (model.length() > 0) {
|
|
|
|
sb.append(" \"x-android-device-model\" \"");
|
|
|
|
sb.append(model);
|
|
|
|
sb.append("\"");
|
2010-01-26 02:40:45 +00:00
|
|
|
}
|
|
|
|
}
|
2010-02-01 23:53:46 +00:00
|
|
|
|
|
|
|
// "x-android-mobile-net-operator" "name of network operator"
|
|
|
|
if (networkOperator.length() > 0) {
|
|
|
|
sb.append(" \"x-android-mobile-net-operator\" \"");
|
|
|
|
sb.append(networkOperator);
|
|
|
|
sb.append("\"");
|
|
|
|
}
|
|
|
|
|
|
|
|
return sb.toString();
|
2010-01-26 02:40:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public Folder getFolder(String name) throws MessagingException {
|
|
|
|
ImapFolder folder;
|
|
|
|
synchronized (mFolderCache) {
|
|
|
|
folder = mFolderCache.get(name);
|
|
|
|
if (folder == null) {
|
2010-05-20 21:47:59 +00:00
|
|
|
folder = new ImapFolder(this, name);
|
2009-03-04 03:32:22 +00:00
|
|
|
mFolderCache.put(name, folder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return folder;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Folder[] getPersonalNamespaces() throws MessagingException {
|
|
|
|
ImapConnection connection = getConnection();
|
|
|
|
try {
|
|
|
|
ArrayList<Folder> folders = new ArrayList<Folder>();
|
2010-05-29 00:34:47 +00:00
|
|
|
List<ImapResponse> responses = connection.executeSimpleCommand(
|
|
|
|
String.format(ImapConstants.LIST + " \"\" \"%s*\"",
|
|
|
|
mPathPrefix == null ? "" : mPathPrefix));
|
2009-03-04 03:32:22 +00:00
|
|
|
for (ImapResponse response : responses) {
|
2010-05-20 00:23:23 +00:00
|
|
|
// S: * LIST (\Noselect) "/" ~/Mail/foo
|
|
|
|
if (response.isDataResponse(0, ImapConstants.LIST)) {
|
2009-03-04 03:32:22 +00:00
|
|
|
boolean includeFolder = true;
|
2010-05-20 00:23:23 +00:00
|
|
|
|
|
|
|
// Get folder name.
|
|
|
|
ImapString encodedFolder = response.getStringOrEmpty(3);
|
|
|
|
if (encodedFolder.isEmpty()) continue;
|
|
|
|
String folder = decodeFolderName(encodedFolder.getString());
|
|
|
|
if (ImapConstants.INBOX.equalsIgnoreCase(folder)) {
|
2009-03-04 03:32:22 +00:00
|
|
|
continue;
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
|
|
|
|
// Parse attributes.
|
|
|
|
if (response.getListOrEmpty(1).contains(ImapConstants.FLAG_NO_SELECT)) {
|
|
|
|
includeFolder = false;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
if (includeFolder) {
|
|
|
|
folders.add(getFolder(folder));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-05-29 00:34:47 +00:00
|
|
|
folders.add(getFolder(ImapConstants.INBOX));
|
2009-03-04 03:32:22 +00:00
|
|
|
return folders.toArray(new Folder[] {});
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
connection.close();
|
|
|
|
throw new MessagingException("Unable to get folder list.", ioe);
|
|
|
|
} finally {
|
2010-06-01 20:36:53 +00:00
|
|
|
connection.destroyResponses();
|
2010-05-19 00:22:28 +00:00
|
|
|
poolConnection(connection);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2010-06-02 20:25:03 +00:00
|
|
|
public Bundle checkSettings() throws MessagingException {
|
|
|
|
int result = MessagingException.NO_ERROR;
|
|
|
|
Bundle bundle = new Bundle();
|
2010-06-01 20:36:53 +00:00
|
|
|
ImapConnection connection = new ImapConnection();
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
|
|
|
connection.open();
|
|
|
|
connection.close();
|
2010-05-20 21:47:59 +00:00
|
|
|
} catch (IOException ioe) {
|
2010-06-02 20:25:03 +00:00
|
|
|
bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE, ioe.getMessage());
|
|
|
|
result = MessagingException.IOERROR;
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
|
|
|
connection.destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-06-02 20:25:03 +00:00
|
|
|
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result);
|
|
|
|
return bundle;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-05-19 00:22:28 +00:00
|
|
|
* Gets a connection if one is available from the pool, or creates a new one if not.
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2010-05-19 00:22:28 +00:00
|
|
|
/* package */ ImapConnection getConnection() {
|
|
|
|
ImapConnection connection = null;
|
|
|
|
while ((connection = mConnectionPool.poll()) != null) {
|
|
|
|
try {
|
2010-05-20 00:23:23 +00:00
|
|
|
connection.executeSimpleCommand(ImapConstants.NOOP);
|
2010-05-19 00:22:28 +00:00
|
|
|
break;
|
|
|
|
} catch (MessagingException e) {
|
|
|
|
// Fall through
|
|
|
|
} catch (IOException e) {
|
|
|
|
// Fall through
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
|
|
|
connection.destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-19 00:22:28 +00:00
|
|
|
connection.close();
|
|
|
|
connection = null;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-19 00:22:28 +00:00
|
|
|
if (connection == null) {
|
|
|
|
connection = new ImapConnection();
|
|
|
|
}
|
|
|
|
return connection;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-19 00:22:28 +00:00
|
|
|
/**
|
|
|
|
* Save a {@link ImapConnection} in the pool for reuse.
|
|
|
|
*/
|
|
|
|
/* package */ void poolConnection(ImapConnection connection) {
|
|
|
|
if (connection != null) {
|
|
|
|
mConnectionPool.add(connection);
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-18 20:41:14 +00:00
|
|
|
/* package */ static String encodeFolderName(String name) {
|
2010-06-01 21:14:21 +00:00
|
|
|
// TODO bypass the conversion if name doesn't have special char.
|
2010-05-29 00:34:47 +00:00
|
|
|
ByteBuffer bb = MODIFIED_UTF_7_CHARSET.encode(name);
|
|
|
|
byte[] b = new byte[bb.limit()];
|
|
|
|
bb.get(b);
|
|
|
|
return Utility.fromAscii(b);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-18 20:41:14 +00:00
|
|
|
/* package */ static String decodeFolderName(String name) {
|
2010-06-01 21:14:21 +00:00
|
|
|
// TODO bypass the conversion if name doesn't have special char.
|
2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* Convert the encoded name to US-ASCII, then pass it through the modified UTF-7
|
|
|
|
* decoder and return the Unicode String.
|
|
|
|
*/
|
2010-05-29 00:34:47 +00:00
|
|
|
return MODIFIED_UTF_7_CHARSET.decode(ByteBuffer.wrap(Utility.toAscii(name))).toString();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
/**
|
|
|
|
* Returns UIDs of Messages joined with "," as the separator.
|
|
|
|
*/
|
|
|
|
/* package */ static String joinMessageUids(Message[] messages) {
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
boolean notFirst = false;
|
|
|
|
for (Message m : messages) {
|
|
|
|
if (notFirst) {
|
|
|
|
sb.append(',');
|
|
|
|
}
|
|
|
|
sb.append(m.getUid());
|
|
|
|
notFirst = true;
|
|
|
|
}
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
2010-05-20 21:47:59 +00:00
|
|
|
static class ImapFolder extends Folder {
|
|
|
|
private final ImapStore mStore;
|
2010-05-10 21:20:16 +00:00
|
|
|
private final String mName;
|
2009-03-04 03:32:22 +00:00
|
|
|
private int mMessageCount = -1;
|
|
|
|
private ImapConnection mConnection;
|
|
|
|
private OpenMode mMode;
|
|
|
|
private boolean mExists;
|
|
|
|
|
2010-05-20 21:47:59 +00:00
|
|
|
public ImapFolder(ImapStore store, String name) {
|
|
|
|
mStore = store;
|
|
|
|
mName = name;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-08-05 23:43:39 +00:00
|
|
|
private void destroyResponses() {
|
|
|
|
if (mConnection != null) {
|
|
|
|
mConnection.destroyResponses();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
AI 146134: Add persistence API for remote stores & folders to use while
syncing. This provides a key-value store, per folder, that
can be used by network Stores to record persistent data such
as sync status, server keys, etc.
Note that, by definition, this only applies to remote folders
(e.g. IMAP, POP3). You'll see everywhere that LocalFolder is
passed null, and this is correct - LocalFolder *is* persistent
storage and does not need external help.
Note to reviewers: The core changes are Folder.java,
LocalStore.java, and LocalStoreUnitTests.java, so please give
them the bulk of your reviewer attention. The other files
are just following along with minor API changes. Of those,
the one worth close examination is MessagingController.java,
which is the only place in the system where remote Folders
are bonded with Local Folders and thus where this new API
comes into play.
Note to jham: Can you please take a look at
LocalStore.LocalFolder.setPersistentString() and recommend
better SQL foo than my primitive test-then-update-or-insert
logic, which is not transactional or threadsafe.
BUG=1786939
Automated import of CL 146134
2009-04-14 17:15:07 +00:00
|
|
|
public void open(OpenMode mode, PersistentDataCallbacks callbacks)
|
|
|
|
throws MessagingException {
|
2010-05-20 00:23:23 +00:00
|
|
|
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);
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2010-05-20 00:23:23 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Return the connection to the pool, if exists.
|
|
|
|
close(false);
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
synchronized (this) {
|
|
|
|
mConnection = mStore.getConnection();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
// * 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 {
|
|
|
|
List<ImapResponse> responses = mConnection.executeSimpleCommand(
|
2010-05-29 00:34:47 +00:00
|
|
|
String.format(ImapConstants.SELECT + " \"%s\"",
|
2010-05-20 00:23:23 +00:00
|
|
|
encodeFolderName(mName)));
|
|
|
|
/*
|
|
|
|
* If the command succeeds we expect the folder has been opened read-write
|
|
|
|
* unless we are notified otherwise in the responses.
|
|
|
|
*/
|
|
|
|
mMode = OpenMode.READ_WRITE;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
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)) {
|
2010-03-18 17:11:08 +00:00
|
|
|
mMode = OpenMode.READ_ONLY;
|
2010-05-20 00:23:23 +00:00
|
|
|
} else if (responseCode.is(ImapConstants.READ_WRITE)) {
|
2010-03-18 17:11:08 +00:00
|
|
|
mMode = OpenMode.READ_WRITE;
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
} else if (response.isTagged()) { // Not OK
|
|
|
|
throw new MessagingException("Can't open mailbox: "
|
|
|
|
+ response.getStatusResponseTextOrEmpty());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
if (messageCount == -1) {
|
|
|
|
throw new MessagingException("Did not find message count during select");
|
|
|
|
}
|
|
|
|
mMessageCount = messageCount;
|
|
|
|
mExists = true;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
} catch (IOException ioe) {
|
|
|
|
throw ioExceptionHandler(mConnection, ioe);
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2010-05-20 00:23:23 +00:00
|
|
|
}
|
|
|
|
} catch (MessagingException e) {
|
|
|
|
mExists = false;
|
|
|
|
close(false);
|
|
|
|
throw e;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public boolean isOpen() {
|
2010-05-20 00:23:23 +00:00
|
|
|
return mExists && mConnection != null;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public OpenMode getMode() throws MessagingException {
|
|
|
|
return mMode;
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public void close(boolean expunge) {
|
|
|
|
// TODO implement expunge
|
|
|
|
mMessageCount = -1;
|
|
|
|
synchronized (this) {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2010-05-20 21:47:59 +00:00
|
|
|
mStore.poolConnection(mConnection);
|
2009-03-04 03:32:22 +00:00
|
|
|
mConnection = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public String getName() {
|
|
|
|
return mName;
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public boolean exists() throws MessagingException {
|
|
|
|
if (mExists) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* This method needs to operate in the unselected mode as well as the selected mode
|
|
|
|
* so we must get the connection ourselves if it's not there. We are specifically
|
|
|
|
* not calling checkOpen() since we don't care if the folder is open.
|
|
|
|
*/
|
|
|
|
ImapConnection connection = null;
|
|
|
|
synchronized(this) {
|
|
|
|
if (mConnection == null) {
|
2010-05-20 21:47:59 +00:00
|
|
|
connection = mStore.getConnection();
|
|
|
|
} else {
|
2009-03-04 03:32:22 +00:00
|
|
|
connection = mConnection;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
try {
|
2010-05-29 00:34:47 +00:00
|
|
|
connection.executeSimpleCommand(String.format(
|
|
|
|
ImapConstants.STATUS + " \"%s\" (" + ImapConstants.UIDVALIDITY + ")",
|
2009-03-04 03:32:22 +00:00
|
|
|
encodeFolderName(mName)));
|
|
|
|
mExists = true;
|
|
|
|
return true;
|
2010-05-20 21:47:59 +00:00
|
|
|
|
|
|
|
} catch (MessagingException me) {
|
2009-03-04 03:32:22 +00:00
|
|
|
return false;
|
2010-05-20 21:47:59 +00:00
|
|
|
|
|
|
|
} catch (IOException ioe) {
|
2009-03-04 03:32:22 +00:00
|
|
|
throw ioExceptionHandler(connection, ioe);
|
2010-05-20 21:47:59 +00:00
|
|
|
|
|
|
|
} finally {
|
2010-06-01 20:36:53 +00:00
|
|
|
connection.destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
if (mConnection == null) {
|
2010-05-20 21:47:59 +00:00
|
|
|
mStore.poolConnection(connection);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// IMAP supports folder creation
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-09-25 21:54:32 +00:00
|
|
|
public boolean canCreate(FolderType type) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public boolean create(FolderType type) throws MessagingException {
|
|
|
|
/*
|
|
|
|
* This method needs to operate in the unselected mode as well as the selected mode
|
|
|
|
* so we must get the connection ourselves if it's not there. We are specifically
|
|
|
|
* not calling checkOpen() since we don't care if the folder is open.
|
|
|
|
*/
|
|
|
|
ImapConnection connection = null;
|
|
|
|
synchronized(this) {
|
|
|
|
if (mConnection == null) {
|
2010-05-20 21:47:59 +00:00
|
|
|
connection = mStore.getConnection();
|
|
|
|
} else {
|
2009-03-04 03:32:22 +00:00
|
|
|
connection = mConnection;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
try {
|
2010-05-29 00:34:47 +00:00
|
|
|
connection.executeSimpleCommand(String.format(ImapConstants.CREATE + " \"%s\"",
|
2009-03-04 03:32:22 +00:00
|
|
|
encodeFolderName(mName)));
|
|
|
|
return true;
|
2010-05-20 21:47:59 +00:00
|
|
|
|
|
|
|
} catch (MessagingException me) {
|
2009-03-04 03:32:22 +00:00
|
|
|
return false;
|
2010-05-20 21:47:59 +00:00
|
|
|
|
|
|
|
} catch (IOException ioe) {
|
2010-03-30 19:43:28 +00:00
|
|
|
throw ioExceptionHandler(connection, ioe);
|
2010-05-20 21:47:59 +00:00
|
|
|
|
|
|
|
} finally {
|
2010-06-01 20:36:53 +00:00
|
|
|
connection.destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
if (mConnection == null) {
|
2010-05-20 21:47:59 +00:00
|
|
|
mStore.poolConnection(connection);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2010-02-08 21:04:03 +00:00
|
|
|
public void copyMessages(Message[] messages, Folder folder,
|
2009-04-24 18:54:42 +00:00
|
|
|
MessageUpdateCallbacks callbacks) throws MessagingException {
|
2009-03-04 03:32:22 +00:00
|
|
|
checkOpen();
|
|
|
|
try {
|
2010-05-29 00:34:47 +00:00
|
|
|
mConnection.executeSimpleCommand(
|
|
|
|
String.format(ImapConstants.UID_COPY + " %s \"%s\"",
|
|
|
|
joinMessageUids(messages),
|
|
|
|
encodeFolderName(folder.getName())));
|
2010-05-20 21:47:59 +00:00
|
|
|
} catch (IOException ioe) {
|
2009-03-04 03:32:22 +00:00
|
|
|
throw ioExceptionHandler(mConnection, ioe);
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getMessageCount() {
|
|
|
|
return mMessageCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getUnreadMessageCount() throws MessagingException {
|
|
|
|
checkOpen();
|
|
|
|
try {
|
|
|
|
int unreadMessageCount = 0;
|
2010-05-29 00:34:47 +00:00
|
|
|
List<ImapResponse> responses = mConnection.executeSimpleCommand(String.format(
|
|
|
|
ImapConstants.STATUS + " \"%s\" (" + ImapConstants.UNSEEN + ")",
|
|
|
|
encodeFolderName(mName)));
|
2010-05-20 00:23:23 +00:00
|
|
|
// S: * STATUS mboxname (MESSAGES 231 UIDNEXT 44292)
|
2009-03-04 03:32:22 +00:00
|
|
|
for (ImapResponse response : responses) {
|
2010-05-20 00:23:23 +00:00
|
|
|
if (response.isDataResponse(0, ImapConstants.STATUS)) {
|
|
|
|
unreadMessageCount = response.getListOrEmpty(2)
|
|
|
|
.getKeyedStringOrEmpty(ImapConstants.UNSEEN).getNumberOrZero();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return unreadMessageCount;
|
2010-05-20 21:47:59 +00:00
|
|
|
} catch (IOException ioe) {
|
2009-03-04 03:32:22 +00:00
|
|
|
throw ioExceptionHandler(mConnection, ioe);
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void delete(boolean recurse) throws MessagingException {
|
|
|
|
throw new Error("ImapStore.delete() not yet implemented");
|
|
|
|
}
|
|
|
|
|
2010-08-11 21:39:45 +00:00
|
|
|
/* package */ String[] searchForUids(String searchCriteria)
|
2010-05-20 00:23:23 +00:00
|
|
|
throws MessagingException {
|
2009-03-04 03:32:22 +00:00
|
|
|
checkOpen();
|
2010-05-20 00:23:23 +00:00
|
|
|
List<ImapResponse> responses;
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
2010-06-01 20:36:53 +00:00
|
|
|
try {
|
|
|
|
responses = mConnection.executeSimpleCommand(
|
|
|
|
ImapConstants.UID_SEARCH + " " + searchCriteria);
|
|
|
|
} catch (ImapException e) {
|
|
|
|
return Utility.EMPTY_STRINGS; // not found;
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
throw ioExceptionHandler(mConnection, ioe);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-06-01 20:36:53 +00:00
|
|
|
// S: * SEARCH 2 3 6
|
2010-08-11 21:39:45 +00:00
|
|
|
final ArrayList<String> uids = new ArrayList<String>();
|
2010-06-01 20:36:53 +00:00
|
|
|
for (ImapResponse response : responses) {
|
|
|
|
if (!response.isDataResponse(0, ImapConstants.SEARCH)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Found SEARCH response data
|
|
|
|
for (int i = 1; i < response.size(); i++) {
|
|
|
|
ImapString s = response.getStringOrEmpty(i);
|
|
|
|
if (s.isString()) {
|
2010-08-11 21:39:45 +00:00
|
|
|
uids.add(s.getString());
|
2010-06-01 20:36:53 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-08-11 21:39:45 +00:00
|
|
|
return uids.toArray(Utility.EMPTY_STRINGS);
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Message getMessage(String uid) throws MessagingException {
|
|
|
|
checkOpen();
|
|
|
|
|
2010-05-29 00:34:47 +00:00
|
|
|
String[] uids = searchForUids(ImapConstants.UID + " " + uid);
|
2010-05-20 00:23:23 +00:00
|
|
|
for (int i = 0; i < uids.length; i++) {
|
|
|
|
if (uids[i].equals(uid)) {
|
|
|
|
return new ImapMessage(uid, this);
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
|
|
|
throws MessagingException {
|
|
|
|
if (start < 1 || end < 1 || end < start) {
|
2010-05-20 00:23:23 +00:00
|
|
|
throw new MessagingException(String.format("Invalid range: %d %d", start, end));
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
return getMessagesInternal(
|
|
|
|
searchForUids(String.format("%d:%d NOT DELETED", start, end)), listener);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
|
|
|
|
return getMessages(null, listener);
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
|
|
|
throws MessagingException {
|
2010-05-20 00:23:23 +00:00
|
|
|
if (uids == null) {
|
|
|
|
uids = searchForUids("1:* NOT DELETED");
|
|
|
|
}
|
|
|
|
return getMessagesInternal(uids, listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Message[] getMessagesInternal(String[] uids, MessageRetrievalListener listener)
|
|
|
|
throws MessagingException {
|
|
|
|
final ArrayList<Message> messages = new ArrayList<Message>(uids.length);
|
|
|
|
for (int i = 0; i < uids.length; i++) {
|
|
|
|
final String uid = uids[i];
|
|
|
|
final ImapMessage message = new ImapMessage(uid, this);
|
|
|
|
messages.add(message);
|
|
|
|
if (listener != null) {
|
|
|
|
listener.messageRetrieved(message);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
return messages.toArray(Message.EMPTY_ARRAY);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-03-08 19:59:14 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
|
|
|
|
throws MessagingException {
|
2010-03-08 19:59:14 +00:00
|
|
|
try {
|
|
|
|
fetchInternal(messages, fp, listener);
|
|
|
|
} catch (RuntimeException e) { // Probably a parser error.
|
|
|
|
Log.w(Email.LOG_TAG, "Exception detected: " + e.getMessage());
|
2010-08-05 23:43:39 +00:00
|
|
|
if (mConnection != null) {
|
|
|
|
mConnection.logLastDiscourse();
|
|
|
|
}
|
2010-03-08 19:59:14 +00:00
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void fetchInternal(Message[] messages, FetchProfile fp,
|
|
|
|
MessageRetrievalListener listener) throws MessagingException {
|
2010-08-05 23:43:39 +00:00
|
|
|
if (messages.length == 0) {
|
2009-03-04 03:32:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
checkOpen();
|
|
|
|
HashMap<String, Message> messageMap = new HashMap<String, Message>();
|
2010-05-20 00:23:23 +00:00
|
|
|
for (Message m : messages) {
|
|
|
|
messageMap.put(m.getUid(), m);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Figure out what command we are going to run:
|
2010-05-20 00:23:23 +00:00
|
|
|
* FLAGS - UID FETCH (FLAGS)
|
|
|
|
* ENVELOPE - UID FETCH (INTERNALDATE UID RFC822.SIZE FLAGS BODY.PEEK[
|
|
|
|
* HEADER.FIELDS (date subject from content-type to cc)])
|
|
|
|
* STRUCTURE - UID FETCH (BODYSTRUCTURE)
|
|
|
|
* BODY_SANE - UID FETCH (BODY.PEEK[]<0.N>) where N = max bytes returned
|
|
|
|
* BODY - UID FETCH (BODY.PEEK[])
|
|
|
|
* Part - UID FETCH (BODY.PEEK[ID]) where ID = mime part ID
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2010-05-20 00:23:23 +00:00
|
|
|
|
|
|
|
final LinkedHashSet<String> fetchFields = new LinkedHashSet<String>();
|
|
|
|
|
|
|
|
fetchFields.add(ImapConstants.UID);
|
2009-03-04 03:32:22 +00:00
|
|
|
if (fp.contains(FetchProfile.Item.FLAGS)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
fetchFields.add(ImapConstants.FLAGS);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
fetchFields.add(ImapConstants.INTERNALDATE);
|
|
|
|
fetchFields.add(ImapConstants.RFC822_SIZE);
|
|
|
|
fetchFields.add(ImapConstants.FETCH_FIELD_HEADERS);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
if (fp.contains(FetchProfile.Item.STRUCTURE)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
fetchFields.add(ImapConstants.BODYSTRUCTURE);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
if (fp.contains(FetchProfile.Item.BODY_SANE)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
fetchFields.add(ImapConstants.FETCH_FIELD_BODY_PEEK_SANE);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
if (fp.contains(FetchProfile.Item.BODY)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
fetchFields.add(ImapConstants.FETCH_FIELD_BODY_PEEK);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
|
|
|
|
final Part fetchPart = fp.getFirstPart();
|
|
|
|
if (fetchPart != null) {
|
|
|
|
String[] partIds =
|
|
|
|
fetchPart.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
|
|
|
|
if (partIds != null) {
|
|
|
|
fetchFields.add(ImapConstants.FETCH_FIELD_BODY_PEEK_BARE
|
|
|
|
+ "[" + partIds[0] + "]");
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2010-08-05 23:43:39 +00:00
|
|
|
mConnection.sendCommand(String.format(
|
2010-05-29 00:34:47 +00:00
|
|
|
ImapConstants.UID_FETCH + " %s (%s)", joinMessageUids(messages),
|
2009-03-04 03:32:22 +00:00
|
|
|
Utility.combine(fetchFields.toArray(new String[fetchFields.size()]), ' ')
|
|
|
|
), false);
|
|
|
|
ImapResponse response;
|
|
|
|
int messageNumber = 0;
|
|
|
|
do {
|
2010-05-20 00:23:23 +00:00
|
|
|
response = null;
|
|
|
|
try {
|
|
|
|
response = mConnection.readResponse();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
if (!response.isDataResponse(1, ImapConstants.FETCH)) {
|
|
|
|
continue; // Ignore
|
|
|
|
}
|
|
|
|
final ImapList fetchList = response.getListOrEmpty(2);
|
|
|
|
final String uid = fetchList.getKeyedStringOrEmpty(ImapConstants.UID)
|
|
|
|
.getString();
|
|
|
|
if (TextUtils.isEmpty(uid)) continue;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
ImapMessage message = (ImapMessage) messageMap.get(uid);
|
2010-02-24 01:29:24 +00:00
|
|
|
if (message == null) continue;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
if (fp.contains(FetchProfile.Item.FLAGS)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
final ImapList flags =
|
|
|
|
fetchList.getKeyedListOrEmpty(ImapConstants.FLAGS);
|
|
|
|
for (int i = 0, count = flags.size(); i < count; i++) {
|
|
|
|
final ImapString flag = flags.getStringOrEmpty(i);
|
|
|
|
if (flag.is(ImapConstants.FLAG_DELETED)) {
|
|
|
|
message.setFlagInternal(Flag.DELETED, true);
|
|
|
|
} else if (flag.is(ImapConstants.FLAG_ANSWERED)) {
|
|
|
|
message.setFlagInternal(Flag.ANSWERED, true);
|
|
|
|
} else if (flag.is(ImapConstants.FLAG_SEEN)) {
|
|
|
|
message.setFlagInternal(Flag.SEEN, true);
|
|
|
|
} else if (flag.is(ImapConstants.FLAG_FLAGGED)) {
|
|
|
|
message.setFlagInternal(Flag.FLAGGED, true);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
final Date internalDate = fetchList.getKeyedStringOrEmpty(
|
|
|
|
ImapConstants.INTERNALDATE).getDateOrNull();
|
|
|
|
final int size = fetchList.getKeyedStringOrEmpty(
|
|
|
|
ImapConstants.RFC822_SIZE).getNumberOrZero();
|
|
|
|
final String header = fetchList.getKeyedStringOrEmpty(
|
|
|
|
ImapConstants.BODY_BRACKET_HEADER, true).getString();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
message.setInternalDate(internalDate);
|
2010-05-20 00:23:23 +00:00
|
|
|
message.setSize(size);
|
|
|
|
message.parse(Utility.streamFromAsciiString(header));
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
if (fp.contains(FetchProfile.Item.STRUCTURE)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
ImapList bs = fetchList.getKeyedListOrEmpty(
|
|
|
|
ImapConstants.BODYSTRUCTURE);
|
|
|
|
if (!bs.isEmpty()) {
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
2010-05-29 00:34:47 +00:00
|
|
|
parseBodyStructure(bs, message, ImapConstants.TEXT);
|
2010-05-20 00:23:23 +00:00
|
|
|
} catch (MessagingException e) {
|
2009-07-16 21:09:15 +00:00
|
|
|
if (Email.LOGD) {
|
2009-03-04 03:32:22 +00:00
|
|
|
Log.v(Email.LOG_TAG, "Error handling message", e);
|
|
|
|
}
|
|
|
|
message.setBody(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
if (fp.contains(FetchProfile.Item.BODY)
|
|
|
|
|| fp.contains(FetchProfile.Item.BODY_SANE)) {
|
|
|
|
// Body is keyed by "BODY[...".
|
|
|
|
// TOOD Should we accept "RFC822" as well??
|
|
|
|
// The old code didn't really check the key, so it accepted any literal
|
|
|
|
// that first appeared.
|
|
|
|
ImapString body = fetchList.getKeyedStringOrEmpty("BODY[", true);
|
|
|
|
InputStream bodyStream = body.getAsStream();
|
|
|
|
message.parse(bodyStream);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
if (fetchPart != null && fetchPart.getSize() > 0) {
|
|
|
|
InputStream bodyStream =
|
|
|
|
fetchList.getKeyedStringOrEmpty("BODY[", true).getAsStream();
|
|
|
|
String contentType = fetchPart.getContentType();
|
|
|
|
String contentTransferEncoding = fetchPart.getHeader(
|
|
|
|
MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
|
2010-06-01 21:14:21 +00:00
|
|
|
|
|
|
|
// TODO Don't create 2 temp files.
|
|
|
|
// decodeBody creates BinaryTempFileBody, but we could avoid this
|
|
|
|
// if we implement ImapStringBody.
|
|
|
|
// (We'll need to share a temp file. Protect it with a ref-count.)
|
2010-08-18 15:50:45 +00:00
|
|
|
fetchPart.setBody(decodeBody(bodyStream, contentTransferEncoding,
|
|
|
|
fetchPart.getSize(), listener));
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (listener != null) {
|
2010-05-13 21:28:08 +00:00
|
|
|
listener.messageRetrieved(message);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
} while (!response.isTagged());
|
|
|
|
} catch (IOException ioe) {
|
2009-03-04 03:32:22 +00:00
|
|
|
throw ioExceptionHandler(mConnection, ioe);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-08-18 15:50:45 +00:00
|
|
|
/**
|
|
|
|
* Removes any content transfer encoding from the stream and returns a Body.
|
|
|
|
* This code is taken/condensed from MimeUtility.decodeBody
|
|
|
|
*/
|
|
|
|
private Body decodeBody(InputStream in, String contentTransferEncoding, int size,
|
|
|
|
MessageRetrievalListener listener) throws IOException {
|
|
|
|
// Get a properly wrapped input stream
|
|
|
|
in = MimeUtility.getInputStreamForContentTransferEncoding(in, contentTransferEncoding);
|
|
|
|
BinaryTempFileBody tempBody = new BinaryTempFileBody();
|
|
|
|
OutputStream out = tempBody.getOutputStream();
|
|
|
|
byte[] buffer = new byte[COPY_BUFFER_SIZE];
|
|
|
|
int n = 0;
|
|
|
|
int count = 0;
|
|
|
|
while (-1 != (n = in.read(buffer))) {
|
|
|
|
out.write(buffer, 0, n);
|
|
|
|
count += n;
|
|
|
|
if (listener != null) {
|
|
|
|
listener.loadAttachmentProgress(count * 100 / size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out.close();
|
|
|
|
return tempBody;
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public Flag[] getPermanentFlags() throws MessagingException {
|
|
|
|
return PERMANENT_FLAGS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle any untagged responses that the caller doesn't care to handle themselves.
|
|
|
|
* @param responses
|
|
|
|
*/
|
|
|
|
private void handleUntaggedResponses(List<ImapResponse> responses) {
|
|
|
|
for (ImapResponse response : responses) {
|
|
|
|
handleUntaggedResponse(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle an untagged response that the caller doesn't care to handle themselves.
|
|
|
|
* @param response
|
|
|
|
*/
|
|
|
|
private void handleUntaggedResponse(ImapResponse response) {
|
2010-05-20 00:23:23 +00:00
|
|
|
if (response.isDataResponse(1, ImapConstants.EXISTS)) {
|
|
|
|
mMessageCount = response.getStringOrEmpty(0).getNumberOrZero();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-20 21:47:59 +00:00
|
|
|
private static void parseBodyStructure(ImapList bs, Part part, String id)
|
2009-03-04 03:32:22 +00:00
|
|
|
throws MessagingException {
|
2010-05-20 00:23:23 +00:00
|
|
|
if (bs.getElementOrNone(0).isList()) {
|
2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* This is a multipart/*
|
|
|
|
*/
|
|
|
|
MimeMultipart mp = new MimeMultipart();
|
|
|
|
for (int i = 0, count = bs.size(); i < count; i++) {
|
2010-05-20 00:23:23 +00:00
|
|
|
ImapElement e = bs.getElementOrNone(i);
|
|
|
|
if (e.isList()) {
|
2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* For each part in the message we're going to add a new BodyPart and parse
|
|
|
|
* into it.
|
|
|
|
*/
|
2010-05-20 21:47:59 +00:00
|
|
|
MimeBodyPart bp = new MimeBodyPart();
|
2010-05-29 00:34:47 +00:00
|
|
|
if (id.equals(ImapConstants.TEXT)) {
|
2010-05-20 00:23:23 +00:00
|
|
|
parseBodyStructure(bs.getListOrEmpty(i), bp, Integer.toString(i + 1));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
parseBodyStructure(bs.getListOrEmpty(i), bp, id + "." + (i + 1));
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
mp.addBodyPart(bp);
|
2010-05-20 00:23:23 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
if (e.isString()) {
|
|
|
|
mp.setSubType(bs.getStringOrEmpty(i).getString().toLowerCase());
|
|
|
|
}
|
|
|
|
break; // Ignore the rest of the list.
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
part.setBody(mp);
|
2010-05-20 21:47:59 +00:00
|
|
|
} else {
|
2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* This is a body. We need to add as much information as we can find out about
|
|
|
|
* it to the Part.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
body type
|
|
|
|
body subtype
|
|
|
|
body parameter parenthesized list
|
|
|
|
body id
|
|
|
|
body description
|
|
|
|
body encoding
|
|
|
|
body size
|
|
|
|
*/
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
final ImapString type = bs.getStringOrEmpty(0);
|
|
|
|
final ImapString subType = bs.getStringOrEmpty(1);
|
|
|
|
final String mimeType =
|
|
|
|
(type.getString() + "/" + subType.getString()).toLowerCase();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
final ImapList bodyParams = bs.getListOrEmpty(2);
|
|
|
|
final ImapString cid = bs.getStringOrEmpty(3);
|
|
|
|
final ImapString encoding = bs.getStringOrEmpty(5);
|
|
|
|
final int size = bs.getStringOrEmpty(6).getNumberOrZero();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 21:47:59 +00:00
|
|
|
if (MimeUtility.mimeTypeMatches(mimeType, MimeUtility.MIME_TYPE_RFC822)) {
|
|
|
|
// A body type of type MESSAGE and subtype RFC822
|
|
|
|
// contains, immediately after the basic fields, the
|
|
|
|
// envelope structure, body structure, and size in
|
|
|
|
// text lines of the encapsulated message.
|
|
|
|
// [MESSAGE, RFC822, [NAME, filename.eml], NIL, NIL, 7BIT, 5974, NIL,
|
|
|
|
// [INLINE, [FILENAME*0, Fwd: Xxx..., FILENAME*1, filename.eml]], NIL]
|
2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* This will be caught by fetch and handled appropriately.
|
|
|
|
*/
|
2010-05-20 21:47:59 +00:00
|
|
|
throw new MessagingException("BODYSTRUCTURE " + MimeUtility.MIME_TYPE_RFC822
|
|
|
|
+ " not yet supported.");
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the content type with as much information as we know right now.
|
|
|
|
*/
|
2010-05-20 00:23:23 +00:00
|
|
|
final StringBuilder contentType = new StringBuilder(mimeType);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
/*
|
|
|
|
* If there are body params we might be able to get some more information out
|
|
|
|
* of them.
|
|
|
|
*/
|
|
|
|
for (int i = 1, count = bodyParams.size(); i < count; i += 2) {
|
|
|
|
|
|
|
|
// TODO We need to convert " into %22, but
|
|
|
|
// because MimeUtility.getHeaderParameter doesn't recognize it,
|
|
|
|
// we can't fix it for now.
|
|
|
|
contentType.append(String.format(";\n %s=\"%s\"",
|
|
|
|
bodyParams.getStringOrEmpty(i - 1).getString(),
|
|
|
|
bodyParams.getStringOrEmpty(i).getString()));
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType.toString());
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
// Extension items
|
2010-05-20 00:23:23 +00:00
|
|
|
final ImapList bodyDisposition;
|
|
|
|
|
2010-05-29 00:34:47 +00:00
|
|
|
if (type.is(ImapConstants.TEXT) && bs.getElementOrNone(9).isList()) {
|
2010-05-20 00:23:23 +00:00
|
|
|
// If media-type is TEXT, 9th element might be: [body-fld-lines] := number
|
|
|
|
// So, if it's not a list, use 10th element.
|
|
|
|
// (Couldn't find evidence in the RFC if it's ALWAYS 10th element.)
|
|
|
|
bodyDisposition = bs.getListOrEmpty(9);
|
|
|
|
} else {
|
|
|
|
bodyDisposition = bs.getListOrEmpty(8);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
final StringBuilder contentDisposition = new StringBuilder();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
if (bodyDisposition.size() > 0) {
|
|
|
|
final String bodyDisposition0Str =
|
|
|
|
bodyDisposition.getStringOrEmpty(0).getString().toLowerCase();
|
|
|
|
if (!TextUtils.isEmpty(bodyDisposition0Str)) {
|
|
|
|
contentDisposition.append(bodyDisposition0Str);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
final ImapList bodyDispositionParams = bodyDisposition.getListOrEmpty(1);
|
|
|
|
if (!bodyDispositionParams.isEmpty()) {
|
2009-03-04 03:32:22 +00:00
|
|
|
/*
|
2010-05-20 00:23:23 +00:00
|
|
|
* If there is body disposition information we can pull some more
|
|
|
|
* information about the attachment out.
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2010-05-20 00:23:23 +00:00
|
|
|
for (int i = 1, count = bodyDispositionParams.size(); i < count; i += 2) {
|
|
|
|
|
|
|
|
// TODO We need to convert " into %22. See above.
|
|
|
|
contentDisposition.append(String.format(";\n %s=\"%s\"",
|
|
|
|
bodyDispositionParams.getStringOrEmpty(i - 1)
|
|
|
|
.getString().toLowerCase(),
|
|
|
|
bodyDispositionParams.getStringOrEmpty(i).getString()));
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
if ((size > 0)
|
|
|
|
&& (MimeUtility.getHeaderParameter(contentDisposition.toString(), "size")
|
|
|
|
== null)) {
|
|
|
|
contentDisposition.append(String.format(";\n size=%d", size));
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
if (contentDisposition.length() > 0) {
|
|
|
|
/*
|
|
|
|
* Set the content disposition containing at least the size. Attachment
|
|
|
|
* handling code will use this down the road.
|
|
|
|
*/
|
|
|
|
part.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
|
|
|
|
contentDisposition.toString());
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the Content-Transfer-Encoding header. Attachment code will use this
|
|
|
|
* to parse the body.
|
|
|
|
*/
|
2010-05-20 00:23:23 +00:00
|
|
|
if (!encoding.isEmpty()) {
|
|
|
|
part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING,
|
|
|
|
encoding.getString());
|
|
|
|
}
|
|
|
|
|
2009-03-19 00:39:48 +00:00
|
|
|
/*
|
|
|
|
* Set the Content-ID header.
|
|
|
|
*/
|
2010-05-20 00:23:23 +00:00
|
|
|
if (!cid.isEmpty()) {
|
|
|
|
part.setHeader(MimeHeader.HEADER_CONTENT_ID, cid.getString());
|
2009-03-19 00:39:48 +00:00
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-05-20 00:23:23 +00:00
|
|
|
if (size > 0) {
|
|
|
|
if (part instanceof ImapMessage) {
|
|
|
|
((ImapMessage) part).setSize(size);
|
|
|
|
} else if (part instanceof MimeBodyPart) {
|
|
|
|
((MimeBodyPart) part).setSize(size);
|
|
|
|
} else {
|
|
|
|
throw new MessagingException("Unknown part type " + part.toString());
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, id);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Appends the given messages to the selected folder. This implementation also determines
|
|
|
|
* the new UID of the given message on the IMAP server and sets the Message's UID to the
|
|
|
|
* new server UID.
|
|
|
|
*/
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public void appendMessages(Message[] messages) throws MessagingException {
|
|
|
|
checkOpen();
|
|
|
|
try {
|
|
|
|
for (Message message : messages) {
|
2009-09-25 23:42:36 +00:00
|
|
|
// Create output count
|
2009-03-04 03:32:22 +00:00
|
|
|
CountingOutputStream out = new CountingOutputStream();
|
|
|
|
EOLConvertingOutputStream eolOut = new EOLConvertingOutputStream(out);
|
|
|
|
message.writeTo(eolOut);
|
|
|
|
eolOut.flush();
|
2009-09-25 23:42:36 +00:00
|
|
|
// Create flag list (most often this will be "\SEEN")
|
|
|
|
String flagList = "";
|
|
|
|
Flag[] flags = message.getFlags();
|
|
|
|
if (flags.length > 0) {
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (int i = 0, count = flags.length; i < count; i++) {
|
|
|
|
Flag flag = flags[i];
|
|
|
|
if (flag == Flag.SEEN) {
|
2010-05-29 00:34:47 +00:00
|
|
|
sb.append(" " + ImapConstants.FLAG_SEEN);
|
2009-09-25 23:42:36 +00:00
|
|
|
} else if (flag == Flag.FLAGGED) {
|
2010-05-29 00:34:47 +00:00
|
|
|
sb.append(" " + ImapConstants.FLAG_FLAGGED);
|
2009-09-25 23:42:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sb.length() > 0) {
|
|
|
|
flagList = sb.substring(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
mConnection.sendCommand(
|
2010-05-29 00:34:47 +00:00
|
|
|
String.format(ImapConstants.APPEND + " \"%s\" (%s) {%d}",
|
2009-03-04 03:32:22 +00:00
|
|
|
encodeFolderName(mName),
|
2009-09-25 23:42:36 +00:00
|
|
|
flagList,
|
2009-03-04 03:32:22 +00:00
|
|
|
out.getCount()), false);
|
|
|
|
ImapResponse response;
|
|
|
|
do {
|
|
|
|
response = mConnection.readResponse();
|
2010-05-20 00:23:23 +00:00
|
|
|
if (response.isContinuationRequest()) {
|
|
|
|
eolOut = new EOLConvertingOutputStream(
|
|
|
|
mConnection.mTransport.getOutputStream());
|
2009-03-04 03:32:22 +00:00
|
|
|
message.writeTo(eolOut);
|
|
|
|
eolOut.write('\r');
|
|
|
|
eolOut.write('\n');
|
|
|
|
eolOut.flush();
|
2010-05-20 00:23:23 +00:00
|
|
|
} else if (!response.isTagged()) {
|
2009-03-04 03:32:22 +00:00
|
|
|
handleUntaggedResponse(response);
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
} while (!response.isTagged());
|
|
|
|
|
|
|
|
// TODO Why not check the response?
|
|
|
|
|
2010-03-18 17:11:08 +00:00
|
|
|
/*
|
|
|
|
* Try to recover the UID of the message from an APPENDUID response.
|
|
|
|
* e.g. 11 OK [APPENDUID 2 238268] APPEND completed
|
|
|
|
*/
|
2010-05-20 00:23:23 +00:00
|
|
|
final ImapList appendList = response.getListOrEmpty(1);
|
|
|
|
if ((appendList.size() >= 3) && appendList.is(0, ImapConstants.APPENDUID)) {
|
|
|
|
String serverUid = appendList.getStringOrEmpty(2).getString();
|
|
|
|
if (!TextUtils.isEmpty(serverUid)) {
|
|
|
|
message.setUid(serverUid);
|
|
|
|
continue;
|
|
|
|
}
|
2010-03-18 17:11:08 +00:00
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Try to find the UID of the message we just appended using the
|
2009-09-25 21:54:32 +00:00
|
|
|
* Message-ID header. If there are more than one response, take the
|
2010-03-18 17:11:08 +00:00
|
|
|
* last one, as it's most likely the newest (the one we just uploaded).
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2010-03-10 04:30:47 +00:00
|
|
|
String messageId = message.getMessageId();
|
|
|
|
if (messageId == null || messageId.length() == 0) {
|
2009-03-04 03:32:22 +00:00
|
|
|
continue;
|
|
|
|
}
|
2010-05-20 00:23:23 +00:00
|
|
|
String[] uids = searchForUids(
|
|
|
|
String.format("(HEADER MESSAGE-ID %s)", messageId));
|
|
|
|
if (uids.length > 0) {
|
|
|
|
message.setUid(uids[0]);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2010-05-20 21:47:59 +00:00
|
|
|
} catch (IOException ioe) {
|
2009-03-04 03:32:22 +00:00
|
|
|
throw ioExceptionHandler(mConnection, ioe);
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public Message[] expunge() throws MessagingException {
|
|
|
|
checkOpen();
|
|
|
|
try {
|
2010-05-29 00:34:47 +00:00
|
|
|
handleUntaggedResponses(mConnection.executeSimpleCommand(ImapConstants.EXPUNGE));
|
2009-03-04 03:32:22 +00:00
|
|
|
} catch (IOException ioe) {
|
|
|
|
throw ioExceptionHandler(mConnection, ioe);
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2010-03-10 04:30:47 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public void setFlags(Message[] messages, Flag[] flags, boolean value)
|
|
|
|
throws MessagingException {
|
|
|
|
checkOpen();
|
2009-08-27 06:12:02 +00:00
|
|
|
|
2010-04-02 18:09:12 +00:00
|
|
|
String allFlags = "";
|
|
|
|
if (flags.length > 0) {
|
|
|
|
StringBuilder flagList = new StringBuilder();
|
|
|
|
for (int i = 0, count = flags.length; i < count; i++) {
|
|
|
|
Flag flag = flags[i];
|
|
|
|
if (flag == Flag.SEEN) {
|
2010-05-29 00:34:47 +00:00
|
|
|
flagList.append(" " + ImapConstants.FLAG_SEEN);
|
2010-04-02 18:09:12 +00:00
|
|
|
} else if (flag == Flag.DELETED) {
|
2010-05-29 00:34:47 +00:00
|
|
|
flagList.append(" " + ImapConstants.FLAG_DELETED);
|
2010-04-02 18:09:12 +00:00
|
|
|
} else if (flag == Flag.FLAGGED) {
|
2010-05-29 00:34:47 +00:00
|
|
|
flagList.append(" " + ImapConstants.FLAG_FLAGGED);
|
2010-04-02 18:09:12 +00:00
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-04-02 18:09:12 +00:00
|
|
|
allFlags = flagList.substring(1);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
try {
|
2010-05-29 00:34:47 +00:00
|
|
|
mConnection.executeSimpleCommand(String.format(
|
|
|
|
ImapConstants.UID_STORE + " %s %s" + ImapConstants.FLAGS_SILENT + " (%s)",
|
2010-05-20 00:23:23 +00:00
|
|
|
joinMessageUids(messages),
|
2009-03-04 03:32:22 +00:00
|
|
|
value ? "+" : "-",
|
2010-04-02 18:09:12 +00:00
|
|
|
allFlags));
|
2010-05-20 21:47:59 +00:00
|
|
|
|
|
|
|
} catch (IOException ioe) {
|
2009-03-04 03:32:22 +00:00
|
|
|
throw ioExceptionHandler(mConnection, ioe);
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
2010-08-05 23:43:39 +00:00
|
|
|
destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void checkOpen() throws MessagingException {
|
|
|
|
if (!isOpen()) {
|
|
|
|
throw new MessagingException("Folder " + mName + " is not open.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private MessagingException ioExceptionHandler(ImapConnection connection, IOException ioe)
|
|
|
|
throws MessagingException {
|
2010-08-05 23:43:39 +00:00
|
|
|
if (Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "IO Exception detected: ", ioe);
|
|
|
|
}
|
|
|
|
connection.destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
connection.close();
|
2010-08-05 23:43:39 +00:00
|
|
|
if (connection == mConnection) {
|
|
|
|
mConnection = null; // To prevent close() from returning the connection to the pool.
|
|
|
|
close(false);
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
return new MessagingException("IO Error", ioe);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
if (o instanceof ImapFolder) {
|
|
|
|
return ((ImapFolder)o).mName.equals(mName);
|
|
|
|
}
|
|
|
|
return super.equals(o);
|
|
|
|
}
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public Message createMessage(String uid) throws MessagingException {
|
|
|
|
return new ImapMessage(uid, this);
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A cacheable class that stores the details for a single IMAP connection.
|
|
|
|
*/
|
|
|
|
class ImapConnection {
|
2010-03-08 19:59:14 +00:00
|
|
|
private static final String IMAP_DEDACTED_LOG = "[IMAP command redacted]";
|
2009-03-04 03:32:22 +00:00
|
|
|
private Transport mTransport;
|
|
|
|
private ImapResponseParser mParser;
|
2010-03-08 19:59:14 +00:00
|
|
|
/** # of command/response lines to log upon crash. */
|
|
|
|
private static final int DISCOURSE_LOGGER_SIZE = 64;
|
|
|
|
private final DiscourseLogger mDiscourse = new DiscourseLogger(DISCOURSE_LOGGER_SIZE);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
public void open() throws IOException, MessagingException {
|
|
|
|
if (mTransport != null && mTransport.isOpen()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
// copy configuration into a clean transport, if necessary
|
|
|
|
if (mTransport == null) {
|
|
|
|
mTransport = mRootTransport.newInstanceWithConfiguration();
|
|
|
|
}
|
2010-02-08 21:04:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
mTransport.open();
|
|
|
|
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
|
|
|
|
2010-06-01 20:36:53 +00:00
|
|
|
createParser();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
// BANNER
|
|
|
|
mParser.readResponse();
|
|
|
|
|
2010-02-26 06:48:11 +00:00
|
|
|
// CAPABILITY
|
2010-05-20 00:23:23 +00:00
|
|
|
ImapResponse capabilityResponse = null;
|
|
|
|
for (ImapResponse r : executeSimpleCommand(ImapConstants.CAPABILITY)) {
|
|
|
|
if (r.is(0, ImapConstants.CAPABILITY)) {
|
|
|
|
capabilityResponse = r;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (capabilityResponse == null) {
|
2010-02-26 06:48:11 +00:00
|
|
|
throw new MessagingException("Invalid CAPABILITY response received");
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
if (mTransport.canTryTlsSecurity()) {
|
2010-05-20 00:23:23 +00:00
|
|
|
if (capabilityResponse.contains(ImapConstants.STARTTLS)) {
|
2009-03-04 03:32:22 +00:00
|
|
|
// STARTTLS
|
2010-05-20 00:23:23 +00:00
|
|
|
executeSimpleCommand(ImapConstants.STARTTLS);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
mTransport.reopenTls();
|
|
|
|
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
2010-06-01 20:36:53 +00:00
|
|
|
createParser();
|
2009-09-29 22:28:43 +00:00
|
|
|
} else {
|
2009-03-04 03:32:22 +00:00
|
|
|
if (Config.LOGD && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, "TLS not supported but required");
|
|
|
|
}
|
|
|
|
throw new MessagingException(MessagingException.TLS_REQUIRED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-26 06:48:11 +00:00
|
|
|
// Assign user-agent string (for RFC2971 ID command)
|
|
|
|
String mUserAgent = getImapId(mContext, mUsername, mRootTransport.getHost(),
|
2010-06-14 16:55:41 +00:00
|
|
|
capabilityResponse);
|
2010-02-26 06:48:11 +00:00
|
|
|
if (mUserAgent != null) {
|
2010-05-29 00:34:47 +00:00
|
|
|
mIdPhrase = ImapConstants.ID + " (" + mUserAgent + ")";
|
2010-02-26 06:48:11 +00:00
|
|
|
} else if (DEBUG_FORCE_SEND_ID) {
|
2010-05-29 00:34:47 +00:00
|
|
|
mIdPhrase = ImapConstants.ID + " " + ImapConstants.NIL;
|
2010-02-26 06:48:11 +00:00
|
|
|
}
|
|
|
|
// else: mIdPhrase = null, no ID will be emitted
|
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
// Send user-agent in an RFC2971 ID command
|
|
|
|
if (mIdPhrase != null) {
|
|
|
|
try {
|
|
|
|
executeSimpleCommand(mIdPhrase);
|
|
|
|
} catch (ImapException ie) {
|
|
|
|
// Log for debugging, but this is not a fatal problem.
|
|
|
|
if (Config.LOGD && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, ie.toString());
|
|
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
// Special case to handle malformed OK responses and ignore them.
|
|
|
|
// A true IOException will recur on the following login steps
|
|
|
|
// This can go away after the parser is fixed - see bug 2138981 for details
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
|
|
|
// TODO eventually we need to add additional authentication
|
|
|
|
// options such as SASL
|
|
|
|
executeSimpleCommand(mLoginPhrase, true);
|
|
|
|
} catch (ImapException ie) {
|
|
|
|
if (Config.LOGD && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, ie.toString());
|
|
|
|
}
|
|
|
|
throw new AuthenticationFailedException(ie.getAlertText(), ie);
|
|
|
|
|
|
|
|
} catch (MessagingException me) {
|
|
|
|
throw new AuthenticationFailedException(null, me);
|
|
|
|
}
|
|
|
|
} catch (SSLException e) {
|
|
|
|
if (Config.LOGD && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, e.toString());
|
|
|
|
}
|
|
|
|
throw new CertificateValidationException(e.getMessage(), e);
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
// NOTE: Unlike similar code in POP3, I'm going to rethrow as-is. There is a lot
|
|
|
|
// of other code here that catches IOException and I don't want to break it.
|
|
|
|
// This catch is only here to enhance logging of connection-time issues.
|
|
|
|
if (Config.LOGD && Email.DEBUG) {
|
|
|
|
Log.d(Email.LOG_TAG, ioe.toString());
|
|
|
|
}
|
|
|
|
throw ioe;
|
2010-06-01 20:36:53 +00:00
|
|
|
} finally {
|
|
|
|
destroyResponses();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void close() {
|
|
|
|
if (mTransport != null) {
|
|
|
|
mTransport.close();
|
2010-05-19 00:22:28 +00:00
|
|
|
mTransport = null;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-01 20:36:53 +00:00
|
|
|
/**
|
|
|
|
* 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(), mDiscourse);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void destroyResponses() {
|
|
|
|
if (mParser != null) {
|
|
|
|
mParser.destroyResponses();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-19 00:22:28 +00:00
|
|
|
/* package */ boolean isTransportOpenForTest() {
|
|
|
|
return mTransport != null ? mTransport.isOpen() : false;
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
public ImapResponse readResponse() throws IOException, MessagingException {
|
|
|
|
return mParser.readResponse();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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).
|
2010-02-08 21:04:03 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param command The command to send to the server
|
|
|
|
* @param sensitive If true, the command will not be logged
|
2010-02-08 21:04:03 +00:00
|
|
|
* @return Returns the command tag that was sent
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
|
|
|
public String sendCommand(String command, boolean sensitive)
|
|
|
|
throws MessagingException, IOException {
|
|
|
|
open();
|
2010-05-19 00:22:28 +00:00
|
|
|
String tag = Integer.toString(mNextCommandTag.incrementAndGet());
|
2009-03-04 03:32:22 +00:00
|
|
|
String commandToSend = tag + " " + command;
|
2010-03-08 19:59:14 +00:00
|
|
|
mTransport.writeLine(commandToSend, sensitive ? IMAP_DEDACTED_LOG : null);
|
|
|
|
mDiscourse.addSentCommand(sensitive ? IMAP_DEDACTED_LOG : commandToSend);
|
2009-03-04 03:32:22 +00:00
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
public List<ImapResponse> executeSimpleCommand(String command) throws IOException,
|
2010-05-19 00:22:28 +00:00
|
|
|
MessagingException {
|
2009-03-04 03:32:22 +00:00
|
|
|
return executeSimpleCommand(command, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
|
2010-05-19 00:22:28 +00:00
|
|
|
throws IOException, MessagingException {
|
2009-03-04 03:32:22 +00:00
|
|
|
String tag = sendCommand(command, sensitive);
|
|
|
|
ArrayList<ImapResponse> responses = new ArrayList<ImapResponse>();
|
|
|
|
ImapResponse response;
|
|
|
|
do {
|
|
|
|
response = mParser.readResponse();
|
|
|
|
responses.add(response);
|
2010-05-20 00:23:23 +00:00
|
|
|
} while (!response.isTagged());
|
|
|
|
if (!response.isOk()) {
|
2010-06-01 20:36:53 +00:00
|
|
|
final String toString = response.toString();
|
|
|
|
final String alert = response.getAlertTextOrEmpty().getString();
|
|
|
|
destroyResponses();
|
|
|
|
throw new ImapException(toString, alert);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
return responses;
|
|
|
|
}
|
2010-03-08 19:59:14 +00:00
|
|
|
|
|
|
|
/** @see ImapResponseParser#logLastDiscourse() */
|
|
|
|
public void logLastDiscourse() {
|
|
|
|
mDiscourse.logLastDiscourse();
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-20 21:47:59 +00:00
|
|
|
static class ImapMessage extends MimeMessage {
|
2009-03-04 03:32:22 +00:00
|
|
|
ImapMessage(String uid, Folder folder) throws MessagingException {
|
|
|
|
this.mUid = uid;
|
|
|
|
this.mFolder = folder;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setSize(int size) {
|
|
|
|
this.mSize = size;
|
|
|
|
}
|
|
|
|
|
2010-04-02 18:09:12 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public void parse(InputStream in) throws IOException, MessagingException {
|
|
|
|
super.parse(in);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
|
|
|
|
super.setFlag(flag, set);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setFlag(Flag flag, boolean set) throws MessagingException {
|
|
|
|
super.setFlag(flag, set);
|
|
|
|
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-20 21:47:59 +00:00
|
|
|
static class ImapException extends MessagingException {
|
2009-03-04 03:32:22 +00:00
|
|
|
String mAlertText;
|
|
|
|
|
|
|
|
public ImapException(String message, String alertText, Throwable throwable) {
|
|
|
|
super(message, throwable);
|
|
|
|
this.mAlertText = alertText;
|
|
|
|
}
|
|
|
|
|
|
|
|
public ImapException(String message, String alertText) {
|
|
|
|
super(message);
|
|
|
|
this.mAlertText = alertText;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getAlertText() {
|
|
|
|
return mAlertText;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setAlertText(String alertText) {
|
|
|
|
mAlertText = alertText;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|