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;
|
|
|
|
import com.android.email.mail.Store;
|
|
|
|
import com.android.email.mail.Transport;
|
|
|
|
import com.android.email.mail.transport.MailTransport;
|
2011-02-11 23:05:17 +00:00
|
|
|
import com.android.emailcommon.Logging;
|
2011-02-10 02:47:43 +00:00
|
|
|
import com.android.emailcommon.internet.MimeMessage;
|
|
|
|
import com.android.emailcommon.mail.AuthenticationFailedException;
|
|
|
|
import com.android.emailcommon.mail.FetchProfile;
|
|
|
|
import com.android.emailcommon.mail.Flag;
|
|
|
|
import com.android.emailcommon.mail.Folder;
|
|
|
|
import com.android.emailcommon.mail.Message;
|
|
|
|
import com.android.emailcommon.mail.MessagingException;
|
|
|
|
import com.android.emailcommon.mail.Folder.OpenMode;
|
2011-04-19 18:32:06 +00:00
|
|
|
import com.android.emailcommon.provider.EmailContent.Account;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.HostAuth;
|
2011-02-09 01:50:30 +00:00
|
|
|
import com.android.emailcommon.service.EmailServiceProxy;
|
2011-02-11 23:05:17 +00:00
|
|
|
import com.android.emailcommon.utility.LoggingInputStream;
|
|
|
|
import com.android.emailcommon.utility.Utility;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-03-27 00:05:25 +00:00
|
|
|
import android.content.Context;
|
2010-06-02 20:25:03 +00:00
|
|
|
import android.os.Bundle;
|
2009-03-04 03:32:22 +00:00
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
|
|
|
public class Pop3Store extends Store {
|
|
|
|
// All flags defining debug or development code settings must be FALSE
|
|
|
|
// when code is checked in or released.
|
|
|
|
private static boolean DEBUG_FORCE_SINGLE_LINE_UIDL = false;
|
2009-03-19 00:39:48 +00:00
|
|
|
private static boolean DEBUG_LOG_RAW_STREAM = false;
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED };
|
|
|
|
|
|
|
|
private Transport mTransport;
|
|
|
|
private String mUsername;
|
|
|
|
private String mPassword;
|
2010-05-10 21:20:16 +00:00
|
|
|
private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
// /**
|
|
|
|
// * Detected latency, used for usage scaling.
|
|
|
|
// * Usage scaling occurs when it is neccesary to get information about
|
|
|
|
// * messages that could result in large data loads. This value allows
|
|
|
|
// * the code that loads this data to decide between using large downloads
|
|
|
|
// * (high latency) or multiple round trips (low latency) to accomplish
|
|
|
|
// * the same thing.
|
|
|
|
// * Default is Integer.MAX_VALUE implying massive latency so that the large
|
|
|
|
// * download method is used by default until latency data is collected.
|
|
|
|
// */
|
|
|
|
// private int mLatencyMs = Integer.MAX_VALUE;
|
|
|
|
//
|
|
|
|
// /**
|
|
|
|
// * Detected throughput, used for usage scaling.
|
|
|
|
// * Usage scaling occurs when it is neccesary to get information about
|
|
|
|
// * messages that could result in large data loads. This value allows
|
|
|
|
// * the code that loads this data to decide between using large downloads
|
|
|
|
// * (high latency) or multiple round trips (low latency) to accomplish
|
|
|
|
// * the same thing.
|
|
|
|
// * Default is Integer.MAX_VALUE implying massive bandwidth so that the
|
|
|
|
// * large download method is used by default until latency data is
|
|
|
|
// * collected.
|
|
|
|
// */
|
|
|
|
// private int mThroughputKbS = Integer.MAX_VALUE;
|
|
|
|
|
2009-03-27 00:05:25 +00:00
|
|
|
/**
|
|
|
|
* Static named constructor.
|
|
|
|
*/
|
2011-04-19 18:32:06 +00:00
|
|
|
public static Store newInstance(Account account, Context context,
|
|
|
|
PersistentDataCallbacks callbacks) throws MessagingException {
|
|
|
|
return new Pop3Store(context, account);
|
2009-03-27 00:05:25 +00:00
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
2011-04-19 18:32:06 +00:00
|
|
|
* Creates a new store for the given account.
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2011-04-19 18:32:06 +00:00
|
|
|
private Pop3Store(Context context, Account account) throws MessagingException {
|
|
|
|
HostAuth recvAuth = account.getOrCreateHostAuthRecv(context);
|
|
|
|
if (recvAuth == null || !STORE_SCHEME_POP3.equalsIgnoreCase(recvAuth.mProtocol)) {
|
2009-09-29 22:28:43 +00:00
|
|
|
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 = 110;
|
2011-04-19 18:32:06 +00:00
|
|
|
|
|
|
|
// check for security flags and apply changes
|
|
|
|
if ((recvAuth.mFlags & HostAuth.FLAG_SSL) != 0) {
|
2009-09-29 22:28:43 +00:00
|
|
|
connectionSecurity = Transport.CONNECTION_SECURITY_SSL;
|
2009-03-04 03:32:22 +00:00
|
|
|
defaultPort = 995;
|
2011-04-19 18:32:06 +00:00
|
|
|
} else if ((recvAuth.mFlags & HostAuth.FLAG_TLS) != 0) {
|
2009-09-29 22:28:43 +00:00
|
|
|
connectionSecurity = Transport.CONNECTION_SECURITY_TLS;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2011-04-19 18:32:06 +00:00
|
|
|
boolean trustCertificates = ((recvAuth.mFlags & HostAuth.FLAG_TRUST_ALL) != 0);
|
2009-09-29 22:28:43 +00:00
|
|
|
|
2011-04-19 18:32:06 +00:00
|
|
|
int port = defaultPort;
|
|
|
|
if (recvAuth.mPort != HostAuth.PORT_UNKNOWN) {
|
|
|
|
port = recvAuth.mPort;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
mTransport = new MailTransport("POP3");
|
2011-04-19 18:32:06 +00:00
|
|
|
mTransport.setHost(recvAuth.mAddress);
|
|
|
|
mTransport.setPort(port);
|
2009-09-29 22:28:43 +00:00
|
|
|
mTransport.setSecurity(connectionSecurity, trustCertificates);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2011-04-19 18:32:06 +00:00
|
|
|
String[] userInfoParts = recvAuth.getLogin();
|
2009-03-04 03:32:22 +00:00
|
|
|
if (userInfoParts != null) {
|
|
|
|
mUsername = userInfoParts[0];
|
2011-04-19 18:32:06 +00:00
|
|
|
mPassword = userInfoParts[1];
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* For testing only. Injects a different transport. The transport 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) {
|
|
|
|
mTransport = testTransport;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public Folder getFolder(String name) {
|
2009-03-04 03:32:22 +00:00
|
|
|
Folder folder = mFolders.get(name);
|
|
|
|
if (folder == null) {
|
|
|
|
folder = new Pop3Folder(name);
|
|
|
|
mFolders.put(folder.getName(), folder);
|
|
|
|
}
|
|
|
|
return folder;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public Folder[] getAllFolders() {
|
2009-03-04 03:32:22 +00:00
|
|
|
return new Folder[] {
|
|
|
|
getFolder("INBOX"),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used by account setup to test if an account's settings are appropriate. The definition
|
|
|
|
* of "checked" here is simply, can you log into the account and does it meet some minimum set
|
|
|
|
* of feature requirements?
|
2010-05-13 21:28:08 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @throws MessagingException if there was some problem with the account
|
|
|
|
*/
|
|
|
|
@Override
|
2010-06-02 20:25:03 +00:00
|
|
|
public Bundle checkSettings() throws MessagingException {
|
2009-03-04 03:32:22 +00:00
|
|
|
Pop3Folder folder = new Pop3Folder("INBOX");
|
2010-06-02 20:25:03 +00:00
|
|
|
Bundle bundle = null;
|
2010-11-05 06:34:26 +00:00
|
|
|
// Close any open or half-open connections - checkSettings should always be "fresh"
|
|
|
|
if (mTransport.isOpen()) {
|
|
|
|
folder.close(false);
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
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
|
|
|
folder.open(OpenMode.READ_WRITE, null);
|
2010-06-02 20:25:03 +00:00
|
|
|
bundle = folder.checkSettings();
|
2009-03-04 03:32:22 +00:00
|
|
|
} finally {
|
|
|
|
folder.close(false); // false == don't expunge anything
|
|
|
|
}
|
2010-06-02 20:25:03 +00:00
|
|
|
return bundle;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class Pop3Folder extends Folder {
|
2010-05-10 21:20:16 +00:00
|
|
|
private final HashMap<String, Pop3Message> mUidToMsgMap
|
|
|
|
= new HashMap<String, Pop3Message>();
|
|
|
|
private final HashMap<Integer, Pop3Message> mMsgNumToMsgMap
|
|
|
|
= new HashMap<Integer, Pop3Message>();
|
|
|
|
private final HashMap<String, Integer> mUidToMsgNumMap = new HashMap<String, Integer>();
|
|
|
|
private final String mName;
|
2009-03-04 03:32:22 +00:00
|
|
|
private int mMessageCount;
|
|
|
|
private Pop3Capabilities mCapabilities;
|
|
|
|
|
|
|
|
public Pop3Folder(String name) {
|
2010-05-10 21:20:16 +00:00
|
|
|
if (name.equalsIgnoreCase("INBOX")) {
|
2009-03-04 03:32:22 +00:00
|
|
|
mName = "INBOX";
|
2010-05-10 21:20:16 +00:00
|
|
|
} else {
|
|
|
|
mName = name;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Used by account setup to test if an account's settings are appropriate. Here, we run
|
|
|
|
* an additional test to see if UIDL is supported on the server. If it's not we
|
|
|
|
* can't service this account.
|
2010-05-13 21:28:08 +00:00
|
|
|
*
|
2010-06-02 20:25:03 +00:00
|
|
|
* @return Bundle containing validation data (code and, if appropriate, error message)
|
2009-03-04 03:32:22 +00:00
|
|
|
* @throws MessagingException if the account is not going to be useable
|
|
|
|
*/
|
2010-06-02 20:25:03 +00:00
|
|
|
public Bundle checkSettings() throws MessagingException {
|
|
|
|
Bundle bundle = new Bundle();
|
|
|
|
int result = MessagingException.NO_ERROR;
|
2009-03-04 03:32:22 +00:00
|
|
|
if (!mCapabilities.uidl) {
|
|
|
|
try {
|
|
|
|
UidlParser parser = new UidlParser();
|
|
|
|
executeSimpleCommand("UIDL");
|
|
|
|
// drain the entire output, so additional communications don't get confused.
|
|
|
|
String response;
|
|
|
|
while ((response = mTransport.readLine()) != null) {
|
|
|
|
parser.parseMultiLine(response);
|
|
|
|
if (parser.mEndOfMessage) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
mTransport.close();
|
2010-06-02 20:25:03 +00:00
|
|
|
result = MessagingException.IOERROR;
|
|
|
|
bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE,
|
|
|
|
ioe.getMessage());
|
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
|
|
|
}
|
|
|
|
|
|
|
|
@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 synchronized void open(OpenMode mode, PersistentDataCallbacks callbacks)
|
|
|
|
throws MessagingException {
|
2009-03-04 03:32:22 +00:00
|
|
|
if (mTransport.isOpen()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mName.equalsIgnoreCase("INBOX")) {
|
|
|
|
throw new MessagingException("Folder does not exist");
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
mTransport.open();
|
|
|
|
|
|
|
|
// Eat the banner
|
|
|
|
executeSimpleCommand(null);
|
|
|
|
|
|
|
|
mCapabilities = getCapabilities();
|
|
|
|
|
|
|
|
if (mTransport.canTryTlsSecurity()) {
|
|
|
|
if (mCapabilities.stls) {
|
2009-03-13 20:04:24 +00:00
|
|
|
executeSimpleCommand("STLS");
|
2009-03-04 03:32:22 +00:00
|
|
|
mTransport.reopenTls();
|
2009-09-29 22:28:43 +00:00
|
|
|
} else {
|
2011-02-02 21:20:01 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "TLS not supported but required");
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
throw new MessagingException(MessagingException.TLS_REQUIRED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
executeSensitiveCommand("USER " + mUsername, "USER /redacted/");
|
|
|
|
executeSensitiveCommand("PASS " + mPassword, "PASS /redacted/");
|
|
|
|
} catch (MessagingException me) {
|
2011-02-02 21:20:01 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, me.toString());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
throw new AuthenticationFailedException(null, me);
|
|
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
2009-09-16 19:20:38 +00:00
|
|
|
mTransport.close();
|
2011-02-02 21:20:01 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, ioe.toString());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
throw new MessagingException(MessagingException.IOERROR, ioe.toString());
|
|
|
|
}
|
|
|
|
|
2009-09-16 19:20:38 +00:00
|
|
|
Exception statException = null;
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
|
|
|
String response = executeSimpleCommand("STAT");
|
|
|
|
String[] parts = response.split(" ");
|
2009-09-16 19:20:38 +00:00
|
|
|
if (parts.length < 2) {
|
|
|
|
statException = new IOException();
|
|
|
|
} else {
|
|
|
|
mMessageCount = Integer.parseInt(parts[1]);
|
|
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
statException = ioe;
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
statException = nfe;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-09-16 19:20:38 +00:00
|
|
|
if (statException != null) {
|
|
|
|
mTransport.close();
|
2011-02-02 21:20:01 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, statException.toString());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-09-16 19:20:38 +00:00
|
|
|
throw new MessagingException("POP3 STAT", statException);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
mUidToMsgMap.clear();
|
|
|
|
mMsgNumToMsgMap.clear();
|
|
|
|
mUidToMsgNumMap.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public OpenMode getMode() {
|
2009-06-25 06:53:32 +00:00
|
|
|
return OpenMode.READ_WRITE;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-05-13 21:28:08 +00:00
|
|
|
* Close the folder (and the transport below it).
|
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* MUST NOT return any exceptions.
|
2010-05-13 21:28:08 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param expunge If true all deleted messages will be expunged (TODO - not implemented)
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void close(boolean expunge) {
|
|
|
|
try {
|
|
|
|
executeSimpleCommand("QUIT");
|
|
|
|
}
|
|
|
|
catch (Exception e) {
|
|
|
|
// ignore any problems here - just continue closing
|
|
|
|
}
|
|
|
|
mTransport.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getName() {
|
|
|
|
return mName;
|
|
|
|
}
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// POP3 does not 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 false;
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public boolean create(FolderType type) {
|
2009-03-04 03:32:22 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public boolean exists() {
|
2009-03-04 03:32:22 +00:00
|
|
|
return mName.equalsIgnoreCase("INBOX");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getMessageCount() {
|
|
|
|
return mMessageCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public int getUnreadMessageCount() {
|
2009-03-04 03:32:22 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Message getMessage(String uid) throws MessagingException {
|
2009-06-25 06:53:32 +00:00
|
|
|
if (mUidToMsgNumMap.size() == 0) {
|
|
|
|
try {
|
|
|
|
indexMsgNums(1, mMessageCount);
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
mTransport.close();
|
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Unable to index during getMessage " + ioe);
|
2009-06-25 06:53:32 +00:00
|
|
|
}
|
|
|
|
throw new MessagingException("getMessages", ioe);
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-06-25 06:53:32 +00:00
|
|
|
Pop3Message message = mUidToMsgMap.get(uid);
|
2009-03-04 03:32:22 +00:00
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
|
|
|
throws MessagingException {
|
|
|
|
if (start < 1 || end < 1 || end < start) {
|
|
|
|
throw new MessagingException(String.format("Invalid message set %d %d",
|
|
|
|
start, end));
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
indexMsgNums(start, end);
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
mTransport.close();
|
2011-02-02 21:20:01 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, ioe.toString());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
throw new MessagingException("getMessages", ioe);
|
|
|
|
}
|
|
|
|
ArrayList<Message> messages = new ArrayList<Message>();
|
|
|
|
for (int msgNum = start; msgNum <= end; msgNum++) {
|
|
|
|
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
|
|
|
|
messages.add(message);
|
|
|
|
if (listener != null) {
|
2010-05-13 21:28:08 +00:00
|
|
|
listener.messageRetrieved(message);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return messages.toArray(new Message[messages.size()]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensures that the given message set (from start to end inclusive)
|
|
|
|
* has been queried so that uids are available in the local cache.
|
|
|
|
* @param start
|
|
|
|
* @param end
|
|
|
|
* @throws MessagingException
|
|
|
|
* @throws IOException
|
|
|
|
*/
|
|
|
|
private void indexMsgNums(int start, int end)
|
|
|
|
throws MessagingException, IOException {
|
|
|
|
int unindexedMessageCount = 0;
|
|
|
|
for (int msgNum = start; msgNum <= end; msgNum++) {
|
|
|
|
if (mMsgNumToMsgMap.get(msgNum) == null) {
|
|
|
|
unindexedMessageCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (unindexedMessageCount == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
UidlParser parser = new UidlParser();
|
|
|
|
if (DEBUG_FORCE_SINGLE_LINE_UIDL ||
|
|
|
|
(unindexedMessageCount < 50 && mMessageCount > 5000)) {
|
|
|
|
/*
|
|
|
|
* In extreme cases we'll do a UIDL command per message instead of a bulk
|
|
|
|
* download.
|
|
|
|
*/
|
|
|
|
for (int msgNum = start; msgNum <= end; msgNum++) {
|
|
|
|
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
|
|
|
|
if (message == null) {
|
|
|
|
String response = executeSimpleCommand("UIDL " + msgNum);
|
2009-09-16 19:20:38 +00:00
|
|
|
if (!parser.parseSingleLine(response)) {
|
|
|
|
throw new IOException();
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
message = new Pop3Message(parser.mUniqueId, this);
|
|
|
|
indexMessage(msgNum, message);
|
|
|
|
}
|
|
|
|
}
|
2009-09-16 19:20:38 +00:00
|
|
|
} else {
|
2009-03-04 03:32:22 +00:00
|
|
|
String response = executeSimpleCommand("UIDL");
|
|
|
|
while ((response = mTransport.readLine()) != null) {
|
2009-09-16 19:20:38 +00:00
|
|
|
if (!parser.parseMultiLine(response)) {
|
|
|
|
throw new IOException();
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
if (parser.mEndOfMessage) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
int msgNum = parser.mMessageNumber;
|
|
|
|
if (msgNum >= start && msgNum <= end) {
|
|
|
|
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
|
|
|
|
if (message == null) {
|
|
|
|
message = new Pop3Message(parser.mUniqueId, this);
|
|
|
|
indexMessage(msgNum, message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void indexUids(ArrayList<String> uids)
|
|
|
|
throws MessagingException, IOException {
|
|
|
|
HashSet<String> unindexedUids = new HashSet<String>();
|
|
|
|
for (String uid : uids) {
|
|
|
|
if (mUidToMsgMap.get(uid) == null) {
|
|
|
|
unindexedUids.add(uid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (unindexedUids.size() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* If we are missing uids in the cache the only sure way to
|
|
|
|
* get them is to do a full UIDL list. A possible optimization
|
|
|
|
* would be trying UIDL for the latest X messages and praying.
|
|
|
|
*/
|
|
|
|
UidlParser parser = new UidlParser();
|
|
|
|
String response = executeSimpleCommand("UIDL");
|
|
|
|
while ((response = mTransport.readLine()) != null) {
|
|
|
|
parser.parseMultiLine(response);
|
|
|
|
if (parser.mEndOfMessage) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (unindexedUids.contains(parser.mUniqueId)) {
|
|
|
|
Pop3Message message = mUidToMsgMap.get(parser.mUniqueId);
|
|
|
|
if (message == null) {
|
|
|
|
message = new Pop3Message(parser.mUniqueId, this);
|
|
|
|
}
|
|
|
|
indexMessage(parser.mMessageNumber, message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Simple parser class for UIDL messages.
|
2010-05-13 21:28:08 +00:00
|
|
|
*
|
|
|
|
* <p>NOTE: In variance with RFC 1939, we allow multiple whitespace between the
|
|
|
|
* message-number and unique-id fields. This provides greater compatibility with some
|
2009-03-04 03:32:22 +00:00
|
|
|
* non-compliant POP3 servers, e.g. mail.comcast.net.
|
|
|
|
*/
|
|
|
|
/* package */ class UidlParser {
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Caller can read back message-number from this field
|
|
|
|
*/
|
|
|
|
public int mMessageNumber;
|
|
|
|
/**
|
|
|
|
* Caller can read back unique-id from this field
|
|
|
|
*/
|
|
|
|
public String mUniqueId;
|
|
|
|
/**
|
|
|
|
* True if the response was "end-of-message"
|
|
|
|
*/
|
|
|
|
public boolean mEndOfMessage;
|
|
|
|
/**
|
|
|
|
* True if an error was reported
|
|
|
|
*/
|
|
|
|
public boolean mErr;
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Construct & Initialize
|
|
|
|
*/
|
|
|
|
public UidlParser() {
|
|
|
|
mErr = true;
|
|
|
|
}
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Parse a single-line response. This is returned from a command of the form
|
2010-05-13 21:28:08 +00:00
|
|
|
* "UIDL msg-num" and will be formatted as: "+OK msg-num unique-id" or
|
2009-03-04 03:32:22 +00:00
|
|
|
* "-ERR diagnostic text"
|
2010-05-13 21:28:08 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param response The string returned from the server
|
|
|
|
* @return true if the string parsed as expected (e.g. no syntax problems)
|
|
|
|
*/
|
|
|
|
public boolean parseSingleLine(String response) {
|
|
|
|
mErr = false;
|
2009-05-22 18:48:14 +00:00
|
|
|
if (response == null || response.length() == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
char first = response.charAt(0);
|
|
|
|
if (first == '+') {
|
|
|
|
String[] uidParts = response.split(" +");
|
|
|
|
if (uidParts.length >= 3) {
|
2009-09-16 19:20:38 +00:00
|
|
|
try {
|
|
|
|
mMessageNumber = Integer.parseInt(uidParts[1]);
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
return false;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
mUniqueId = uidParts[2];
|
|
|
|
mEndOfMessage = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else if (first == '-') {
|
|
|
|
mErr = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Parse a multi-line response. This is returned from a command of the form
|
|
|
|
* "UIDL" and will be formatted as: "." or "msg-num unique-id".
|
2010-05-13 21:28:08 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param response The string returned from the server
|
|
|
|
* @return true if the string parsed as expected (e.g. no syntax problems)
|
|
|
|
*/
|
|
|
|
public boolean parseMultiLine(String response) {
|
|
|
|
mErr = false;
|
2009-05-22 18:48:14 +00:00
|
|
|
if (response == null || response.length() == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
char first = response.charAt(0);
|
|
|
|
if (first == '.') {
|
|
|
|
mEndOfMessage = true;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
String[] uidParts = response.split(" +");
|
|
|
|
if (uidParts.length >= 2) {
|
2009-09-16 19:20:38 +00:00
|
|
|
try {
|
|
|
|
mMessageNumber = Integer.parseInt(uidParts[0]);
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
return false;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
mUniqueId = uidParts[1];
|
|
|
|
mEndOfMessage = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void indexMessage(int msgNum, Pop3Message message) {
|
|
|
|
mMsgNumToMsgMap.put(msgNum, message);
|
|
|
|
mUidToMsgMap.put(message.getUid(), message);
|
|
|
|
mUidToMsgNumMap.put(message.getUid(), msgNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public Message[] getMessages(MessageRetrievalListener listener) {
|
|
|
|
throw new UnsupportedOperationException(
|
|
|
|
"Pop3Folder.getMessage(MessageRetrievalListener)");
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) {
|
|
|
|
throw new UnsupportedOperationException(
|
|
|
|
"Pop3Folder.getMessage(MessageRetrievalListener)");
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetch the items contained in the FetchProfile into the given set of
|
|
|
|
* Messages in as efficient a manner as possible.
|
|
|
|
* @param messages
|
|
|
|
* @param fp
|
|
|
|
* @throws MessagingException
|
|
|
|
*/
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
|
|
|
|
throws MessagingException {
|
|
|
|
if (messages == null || messages.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ArrayList<String> uids = new ArrayList<String>();
|
|
|
|
for (Message message : messages) {
|
|
|
|
uids.add(message.getUid());
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
indexUids(uids);
|
|
|
|
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
|
|
|
|
// Note: We never pass the listener for the ENVELOPE call, because we're going
|
|
|
|
// to be calling the listener below in the per-message loop.
|
|
|
|
fetchEnvelope(messages, null);
|
|
|
|
}
|
2009-09-16 19:20:38 +00:00
|
|
|
} catch (IOException ioe) {
|
2009-03-04 03:32:22 +00:00
|
|
|
mTransport.close();
|
2011-02-02 21:20:01 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, ioe.toString());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
throw new MessagingException("fetch", ioe);
|
|
|
|
}
|
|
|
|
for (int i = 0, count = messages.length; i < count; i++) {
|
|
|
|
Message message = messages[i];
|
|
|
|
if (!(message instanceof Pop3Message)) {
|
|
|
|
throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
|
|
|
|
}
|
|
|
|
Pop3Message pop3Message = (Pop3Message)message;
|
|
|
|
try {
|
|
|
|
if (fp.contains(FetchProfile.Item.BODY)) {
|
|
|
|
fetchBody(pop3Message, -1);
|
|
|
|
}
|
|
|
|
else if (fp.contains(FetchProfile.Item.BODY_SANE)) {
|
|
|
|
/*
|
|
|
|
* To convert the suggested download size we take the size
|
|
|
|
* divided by the maximum line size (76).
|
|
|
|
*/
|
|
|
|
fetchBody(pop3Message,
|
|
|
|
FETCH_BODY_SANE_SUGGESTED_SIZE / 76);
|
|
|
|
}
|
|
|
|
else if (fp.contains(FetchProfile.Item.STRUCTURE)) {
|
|
|
|
/*
|
|
|
|
* If the user is requesting STRUCTURE we are required to set the body
|
|
|
|
* to null since we do not support the function.
|
|
|
|
*/
|
|
|
|
pop3Message.setBody(null);
|
|
|
|
}
|
|
|
|
if (listener != null) {
|
2010-05-13 21:28:08 +00:00
|
|
|
listener.messageRetrieved(message);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
mTransport.close();
|
2011-02-02 21:20:01 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, ioe.toString());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
throw new MessagingException("Unable to fetch message", ioe);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void fetchEnvelope(Message[] messages,
|
|
|
|
MessageRetrievalListener listener) throws IOException, MessagingException {
|
|
|
|
int unsizedMessages = 0;
|
|
|
|
for (Message message : messages) {
|
|
|
|
if (message.getSize() == -1) {
|
|
|
|
unsizedMessages++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (unsizedMessages == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (unsizedMessages < 50 && mMessageCount > 5000) {
|
|
|
|
/*
|
|
|
|
* In extreme cases we'll do a command per message instead of a bulk request
|
|
|
|
* to hopefully save some time and bandwidth.
|
|
|
|
*/
|
|
|
|
for (int i = 0, count = messages.length; i < count; i++) {
|
|
|
|
Message message = messages[i];
|
|
|
|
if (!(message instanceof Pop3Message)) {
|
|
|
|
throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
|
|
|
|
}
|
|
|
|
Pop3Message pop3Message = (Pop3Message)message;
|
|
|
|
String response = executeSimpleCommand(String.format("LIST %d",
|
|
|
|
mUidToMsgNumMap.get(pop3Message.getUid())));
|
2009-09-16 19:20:38 +00:00
|
|
|
try {
|
|
|
|
String[] listParts = response.split(" ");
|
|
|
|
int msgNum = Integer.parseInt(listParts[1]);
|
|
|
|
int msgSize = Integer.parseInt(listParts[2]);
|
|
|
|
pop3Message.setSize(msgSize);
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
throw new IOException();
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
if (listener != null) {
|
2010-05-13 21:28:08 +00:00
|
|
|
listener.messageRetrieved(pop3Message);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2009-09-16 19:20:38 +00:00
|
|
|
} else {
|
2009-03-04 03:32:22 +00:00
|
|
|
HashSet<String> msgUidIndex = new HashSet<String>();
|
|
|
|
for (Message message : messages) {
|
|
|
|
msgUidIndex.add(message.getUid());
|
|
|
|
}
|
|
|
|
String response = executeSimpleCommand("LIST");
|
|
|
|
while ((response = mTransport.readLine()) != null) {
|
|
|
|
if (response.equals(".")) {
|
|
|
|
break;
|
|
|
|
}
|
2009-09-16 19:20:38 +00:00
|
|
|
Pop3Message pop3Message = null;
|
|
|
|
int msgSize = 0;
|
|
|
|
try {
|
|
|
|
String[] listParts = response.split(" ");
|
|
|
|
int msgNum = Integer.parseInt(listParts[0]);
|
|
|
|
msgSize = Integer.parseInt(listParts[1]);
|
|
|
|
pop3Message = mMsgNumToMsgMap.get(msgNum);
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
throw new IOException();
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
if (pop3Message != null && msgUidIndex.contains(pop3Message.getUid())) {
|
|
|
|
pop3Message.setSize(msgSize);
|
|
|
|
if (listener != null) {
|
2010-05-13 21:28:08 +00:00
|
|
|
listener.messageRetrieved(pop3Message);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetches the body of the given message, limiting the stored data
|
|
|
|
* to the specified number of lines. If lines is -1 the entire message
|
|
|
|
* is fetched. This is implemented with RETR for lines = -1 or TOP
|
|
|
|
* for any other value. If the server does not support TOP it is
|
|
|
|
* emulated with RETR and extra lines are thrown away.
|
2010-04-12 17:56:55 +00:00
|
|
|
*
|
|
|
|
* Note: Some servers (e.g. live.com) don't support CAPA, but turn out to
|
|
|
|
* support TOP after all. For better performance on these servers, we'll always
|
|
|
|
* probe TOP, and fall back to RETR when it's truly unsupported.
|
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param message
|
|
|
|
* @param lines
|
|
|
|
*/
|
|
|
|
private void fetchBody(Pop3Message message, int lines)
|
|
|
|
throws IOException, MessagingException {
|
|
|
|
String response = null;
|
2010-04-12 17:56:55 +00:00
|
|
|
int messageId = mUidToMsgNumMap.get(message.getUid());
|
|
|
|
if (lines == -1) {
|
|
|
|
// Fetch entire message
|
|
|
|
response = executeSimpleCommand(String.format("RETR %d", messageId));
|
|
|
|
} else {
|
|
|
|
// Fetch partial message. Try "TOP", and fall back to slower "RETR" if necessary
|
|
|
|
try {
|
|
|
|
response = executeSimpleCommand(String.format("TOP %d %d", messageId, lines));
|
|
|
|
} catch (MessagingException me) {
|
|
|
|
response = executeSimpleCommand(String.format("RETR %d", messageId));
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
if (response != null) {
|
|
|
|
try {
|
2009-03-19 00:39:48 +00:00
|
|
|
InputStream in = mTransport.getInputStream();
|
2011-02-02 21:20:01 +00:00
|
|
|
if (DEBUG_LOG_RAW_STREAM && Email.DEBUG) {
|
2009-03-19 00:39:48 +00:00
|
|
|
in = new LoggingInputStream(in);
|
|
|
|
}
|
|
|
|
message.parse(new Pop3ResponseInputStream(in));
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
catch (MessagingException me) {
|
|
|
|
/*
|
|
|
|
* If we're only downloading headers it's possible
|
|
|
|
* we'll get a broken MIME message which we're not
|
|
|
|
* real worried about. If we've downloaded the body
|
|
|
|
* and can't parse it we need to let the user know.
|
|
|
|
*/
|
|
|
|
if (lines == -1) {
|
|
|
|
throw me;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public Flag[] getPermanentFlags() {
|
2009-03-04 03:32:22 +00:00
|
|
|
return PERMANENT_FLAGS;
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public void appendMessages(Message[] messages) {
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public void delete(boolean recurse) {
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public Message[] expunge() {
|
2009-03-04 03:32:22 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public void setFlags(Message[] messages, Flag[] flags, boolean value)
|
|
|
|
throws MessagingException {
|
|
|
|
if (!value || !Utility.arrayContains(flags, Flag.DELETED)) {
|
|
|
|
/*
|
|
|
|
* The only flagging we support is setting the Deleted flag.
|
|
|
|
*/
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
for (Message message : messages) {
|
|
|
|
executeSimpleCommand(String.format("DELE %s",
|
|
|
|
mUidToMsgNumMap.get(message.getUid())));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (IOException ioe) {
|
|
|
|
mTransport.close();
|
2011-02-02 21:20:01 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, ioe.toString());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
throw new MessagingException("setFlags()", ioe);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public void copyMessages(Message[] msgs, Folder folder, MessageUpdateCallbacks callbacks) {
|
2009-03-04 03:32:22 +00:00
|
|
|
throw new UnsupportedOperationException("copyMessages is not supported in POP3");
|
|
|
|
}
|
|
|
|
|
|
|
|
// private boolean isRoundTripModeSuggested() {
|
|
|
|
// long roundTripMethodMs =
|
|
|
|
// (uncachedMessageCount * 2 * mLatencyMs);
|
|
|
|
// long bulkMethodMs =
|
|
|
|
// (mMessageCount * 58) / (mThroughputKbS * 1024 / 8) * 1000;
|
|
|
|
// }
|
|
|
|
|
2011-04-19 18:32:06 +00:00
|
|
|
private Pop3Capabilities getCapabilities() throws IOException {
|
2009-03-04 03:32:22 +00:00
|
|
|
Pop3Capabilities capabilities = new Pop3Capabilities();
|
|
|
|
try {
|
|
|
|
String response = executeSimpleCommand("CAPA");
|
|
|
|
while ((response = mTransport.readLine()) != null) {
|
|
|
|
if (response.equals(".")) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (response.equalsIgnoreCase("STLS")){
|
|
|
|
capabilities.stls = true;
|
|
|
|
}
|
|
|
|
else if (response.equalsIgnoreCase("UIDL")) {
|
|
|
|
capabilities.uidl = true;
|
|
|
|
}
|
|
|
|
else if (response.equalsIgnoreCase("PIPELINING")) {
|
|
|
|
capabilities.pipelining = true;
|
|
|
|
}
|
|
|
|
else if (response.equalsIgnoreCase("USER")) {
|
|
|
|
capabilities.user = true;
|
|
|
|
}
|
|
|
|
else if (response.equalsIgnoreCase("TOP")) {
|
|
|
|
capabilities.top = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (MessagingException me) {
|
|
|
|
/*
|
|
|
|
* The server may not support the CAPA command, so we just eat this Exception
|
|
|
|
* and allow the empty capabilities object to be returned.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
return capabilities;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send a single command and wait for a single line response. Reopens the connection,
|
|
|
|
* if it is closed. Leaves the connection open.
|
2010-05-13 21:28:08 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param command The command string to send to the server.
|
|
|
|
* @return Returns the response string from the server.
|
|
|
|
*/
|
|
|
|
private String executeSimpleCommand(String command) throws IOException, MessagingException {
|
|
|
|
return executeSensitiveCommand(command, null);
|
|
|
|
}
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Send a single command and wait for a single line response. Reopens the connection,
|
|
|
|
* if it is closed. Leaves the connection open.
|
2010-05-13 21:28:08 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param command The command string to send to the server.
|
|
|
|
* @param sensitiveReplacement If the command includes sensitive data (e.g. authentication)
|
|
|
|
* please pass a replacement string here (for logging).
|
|
|
|
* @return Returns the response string from the server.
|
|
|
|
*/
|
|
|
|
private String executeSensitiveCommand(String command, String sensitiveReplacement)
|
|
|
|
throws IOException, MessagingException {
|
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
|
|
|
open(OpenMode.READ_WRITE, null);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
if (command != null) {
|
|
|
|
mTransport.writeLine(command, sensitiveReplacement);
|
|
|
|
}
|
|
|
|
|
|
|
|
String response = mTransport.readLine();
|
|
|
|
|
|
|
|
if (response.length() > 1 && response.charAt(0) == '-') {
|
|
|
|
throw new MessagingException(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
if (o instanceof Pop3Folder) {
|
|
|
|
return ((Pop3Folder) o).mName.equals(mName);
|
|
|
|
}
|
|
|
|
return super.equals(o);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
// TODO this is deprecated, eventually discard
|
|
|
|
public boolean isOpen() {
|
|
|
|
return mTransport.isOpen();
|
|
|
|
}
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
@Override
|
2011-04-19 18:32:06 +00:00
|
|
|
public Message createMessage(String uid) {
|
2009-08-19 20:15:13 +00:00
|
|
|
return new Pop3Message(uid, this);
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-05-04 22:33:08 +00:00
|
|
|
public static class Pop3Message extends MimeMessage {
|
2011-04-19 18:32:06 +00:00
|
|
|
public Pop3Message(String uid, Pop3Folder folder) {
|
2009-03-04 03:32:22 +00:00
|
|
|
mUid = uid;
|
|
|
|
mFolder = folder;
|
|
|
|
mSize = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setSize(int size) {
|
|
|
|
mSize = size;
|
|
|
|
}
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2010-05-04 22:33:08 +00:00
|
|
|
public void parse(InputStream in) throws IOException, MessagingException {
|
2009-03-04 03:32:22 +00:00
|
|
|
super.parse(in);
|
|
|
|
}
|
|
|
|
|
|
|
|
@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-13 21:28:08 +00:00
|
|
|
/**
|
2009-03-04 03:32:22 +00:00
|
|
|
* POP3 Capabilities as defined in RFC 2449. This is not a complete list of CAPA
|
2010-05-13 21:28:08 +00:00
|
|
|
* responses - just those that we use in this client.
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
|
|
|
class Pop3Capabilities {
|
|
|
|
/** The STLS (start TLS) command is supported */
|
|
|
|
public boolean stls;
|
|
|
|
/** the TOP command (retrieve a partial message) is supported */
|
|
|
|
public boolean top;
|
|
|
|
/** USER and PASS login/auth commands are supported */
|
|
|
|
public boolean user;
|
|
|
|
/** the optional UIDL command is supported (unused) */
|
|
|
|
public boolean uidl;
|
|
|
|
/** the server is capable of accepting multiple commands at a time (unused) */
|
|
|
|
public boolean pipelining;
|
|
|
|
|
2010-05-07 22:26:31 +00:00
|
|
|
@Override
|
2009-03-04 03:32:22 +00:00
|
|
|
public String toString() {
|
|
|
|
return String.format("STLS %b, TOP %b, USER %b, UIDL %b, PIPELINING %b",
|
|
|
|
stls,
|
|
|
|
top,
|
|
|
|
user,
|
|
|
|
uidl,
|
|
|
|
pipelining);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO figure out what is special about this and merge it into MailTransport
|
|
|
|
class Pop3ResponseInputStream extends InputStream {
|
2010-05-10 21:20:16 +00:00
|
|
|
private final InputStream mIn;
|
|
|
|
private boolean mStartOfLine = true;
|
|
|
|
private boolean mFinished;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
public Pop3ResponseInputStream(InputStream in) {
|
|
|
|
mIn = in;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int read() throws IOException {
|
|
|
|
if (mFinished) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
int d = mIn.read();
|
|
|
|
if (mStartOfLine && d == '.') {
|
|
|
|
d = mIn.read();
|
|
|
|
if (d == '\r') {
|
|
|
|
mFinished = true;
|
|
|
|
mIn.read();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mStartOfLine = (d == '\n');
|
|
|
|
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|