mirror of https://github.com/M66B/FairEmail.git
2246 lines
73 KiB
Java
2246 lines
73 KiB
Java
/*
|
|
* Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
|
|
*
|
|
* This program and the accompanying materials are made available under the
|
|
* terms of the Eclipse Public License v. 2.0, which is available at
|
|
* http://www.eclipse.org/legal/epl-2.0.
|
|
*
|
|
* This Source Code may also be made available under the following Secondary
|
|
* Licenses when the conditions for such availability set forth in the
|
|
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
|
* version 2 with the GNU Classpath Exception, which is available at
|
|
* https://www.gnu.org/software/classpath/license.html.
|
|
*
|
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
*/
|
|
|
|
package com.sun.mail.imap;
|
|
|
|
import java.lang.reflect.*;
|
|
import java.util.Vector;
|
|
import java.util.StringTokenizer;
|
|
import java.util.Locale;
|
|
import java.util.Properties;
|
|
import java.io.IOException;
|
|
import java.net.InetAddress;
|
|
import java.net.UnknownHostException;
|
|
import java.util.Map;
|
|
import java.util.HashMap;
|
|
import java.util.logging.Level;
|
|
|
|
import javax.mail.*;
|
|
import javax.mail.event.*;
|
|
|
|
import com.sun.mail.iap.*;
|
|
import com.sun.mail.imap.protocol.*;
|
|
import com.sun.mail.util.PropUtil;
|
|
import com.sun.mail.util.MailLogger;
|
|
import com.sun.mail.util.SocketConnectException;
|
|
import com.sun.mail.util.MailConnectException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* This class provides access to an IMAP message store. <p>
|
|
*
|
|
* Applications that need to make use of IMAP-specific features may cast
|
|
* a <code>Store</code> object to an <code>IMAPStore</code> object and
|
|
* use the methods on this class. The {@link #getQuota getQuota} and
|
|
* {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
|
|
* Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
|
|
* for more information. <p>
|
|
*
|
|
* The {@link #id id} method supports the IMAP ID extension;
|
|
* see <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
|
|
* The fields ID_NAME, ID_VERSION, etc. represent the suggested field names
|
|
* in RFC 2971 section 3.3 and may be used as keys in the Map containing
|
|
* client values or server values. <p>
|
|
*
|
|
* See the <a href="package-summary.html">com.sun.mail.imap</a> package
|
|
* documentation for further information on the IMAP protocol provider. <p>
|
|
*
|
|
* <strong>WARNING:</strong> The APIs unique to this class should be
|
|
* considered <strong>EXPERIMENTAL</strong>. They may be changed in the
|
|
* future in ways that are incompatible with applications using the
|
|
* current APIs.
|
|
*
|
|
* @author John Mani
|
|
* @author Bill Shannon
|
|
* @author Jim Glennon
|
|
*/
|
|
/*
|
|
* This package is implemented over the "imap.protocol" package, which
|
|
* implements the protocol-level commands. <p>
|
|
*
|
|
* A connected IMAPStore maintains a pool of IMAP protocol objects for
|
|
* use in communicating with the IMAP server. The IMAPStore will create
|
|
* the initial AUTHENTICATED connection and seed the pool with this
|
|
* connection. As folders are opened and new IMAP protocol objects are
|
|
* needed, the IMAPStore will provide them from the connection pool,
|
|
* or create them if none are available. When a folder is closed,
|
|
* its IMAP protocol object is returned to the connection pool if the
|
|
* pool is not over capacity. The pool size can be configured by setting
|
|
* the mail.imap.connectionpoolsize property. <p>
|
|
*
|
|
* Note that all connections in the connection pool have their response
|
|
* handler set to be the Store. When the connection is removed from the
|
|
* pool for use by a folder, the response handler is removed and then set
|
|
* to either the Folder or to the special nonStoreResponseHandler, depending
|
|
* on how the connection is being used. This is probably excessive.
|
|
* Better would be for the Protocol object to support only a single
|
|
* response handler, which would be set before the connection is used
|
|
* and cleared when the connection is in the pool and can't be used. <p>
|
|
*
|
|
* A mechanism is provided for timing out idle connection pool IMAP
|
|
* protocol objects. Timed out connections are closed and removed (pruned)
|
|
* from the connection pool. The time out interval can be configured via
|
|
* the mail.imap.connectionpooltimeout property. <p>
|
|
*
|
|
* The connected IMAPStore object may or may not maintain a separate IMAP
|
|
* protocol object that provides the store a dedicated connection to the
|
|
* IMAP server. This is provided mainly for compatibility with previous
|
|
* implementations of Jakarta Mail and is determined by the value of the
|
|
* mail.imap.separatestoreconnection property. <p>
|
|
*
|
|
* An IMAPStore object provides closed IMAPFolder objects thru its list()
|
|
* and listSubscribed() methods. A closed IMAPFolder object acquires an
|
|
* IMAP protocol object from the store to communicate with the server. When
|
|
* the folder is opened, it gets its own protocol object and thus its own,
|
|
* separate connection to the server. The store maintains references to
|
|
* all 'open' folders. When a folder is/gets closed, the store removes
|
|
* it from its list. When the store is/gets closed, it closes all open
|
|
* folders in its list, thus cleaning up all open connections to the
|
|
* server. <p>
|
|
*
|
|
* A mutex is used to control access to the connection pool resources.
|
|
* Any time any of these resources need to be accessed, the following
|
|
* convention should be followed:
|
|
*
|
|
* synchronized (pool) { // ACQUIRE LOCK
|
|
* // access connection pool resources
|
|
* } // RELEASE LOCK <p>
|
|
*
|
|
* The locking relationship between the store and folders is that the
|
|
* store lock must be acquired before a folder lock. This is currently only
|
|
* applicable in the store's cleanup method. It's important that the
|
|
* connection pool lock is not held when calling into folder objects.
|
|
* The locking hierarchy is that a folder lock must be acquired before
|
|
* any connection pool operations are performed. You never need to hold
|
|
* all three locks, but if you hold more than one this is the order you
|
|
* have to acquire them in. <p>
|
|
*
|
|
* That is: Store > Folder, Folder > pool, Store > pool <p>
|
|
*
|
|
* The IMAPStore implements the ResponseHandler interface and listens to
|
|
* BYE or untagged OK-notification events from the server as a result of
|
|
* Store operations. IMAPFolder forwards notifications that result from
|
|
* Folder operations using the store connection; the IMAPStore ResponseHandler
|
|
* is not used directly in this case. <p>
|
|
*/
|
|
|
|
public class IMAPStore extends Store
|
|
implements QuotaAwareStore, ResponseHandler {
|
|
|
|
/**
|
|
* A special event type for a StoreEvent to indicate an IMAP
|
|
* response, if the mail.imap.enableimapevents property is set.
|
|
*/
|
|
public static final int RESPONSE = 1000;
|
|
|
|
public static final String ID_NAME = "name";
|
|
public static final String ID_VERSION = "version";
|
|
public static final String ID_OS = "os";
|
|
public static final String ID_OS_VERSION = "os-version";
|
|
public static final String ID_VENDOR = "vendor";
|
|
public static final String ID_SUPPORT_URL = "support-url";
|
|
public static final String ID_ADDRESS = "address";
|
|
public static final String ID_DATE = "date";
|
|
public static final String ID_COMMAND = "command";
|
|
public static final String ID_ARGUMENTS = "arguments";
|
|
public static final String ID_ENVIRONMENT = "environment";
|
|
|
|
protected final String name; // name of this protocol
|
|
protected final int defaultPort; // default IMAP port
|
|
protected final boolean isSSL; // use SSL?
|
|
|
|
private final int blksize; // Block size for data requested
|
|
// in FETCH requests. Defaults to
|
|
// 16K
|
|
|
|
private boolean ignoreSize; // ignore the size in BODYSTRUCTURE?
|
|
|
|
private final int statusCacheTimeout; // cache Status for 1 second
|
|
|
|
private final int appendBufferSize; // max size of msg buffered for append
|
|
|
|
private final int minIdleTime; // minimum idle time
|
|
|
|
private volatile int port = -1; // port to use
|
|
|
|
// Auth info
|
|
protected String host;
|
|
protected String user;
|
|
protected String password;
|
|
protected String proxyAuthUser;
|
|
protected String authorizationID;
|
|
protected String saslRealm;
|
|
|
|
private Namespaces namespaces;
|
|
|
|
private boolean enableStartTLS = false; // enable STARTTLS
|
|
private boolean requireStartTLS = false; // require STARTTLS
|
|
private boolean usingSSL = false; // using SSL?
|
|
private boolean enableSASL = false; // enable SASL authentication
|
|
private String[] saslMechanisms;
|
|
private boolean forcePasswordRefresh = false;
|
|
// enable notification of IMAP responses
|
|
private boolean enableResponseEvents = false;
|
|
// enable notification of IMAP responses during IDLE
|
|
private boolean enableImapEvents = false;
|
|
private String guid; // for Yahoo! Mail IMAP
|
|
private boolean throwSearchException = false;
|
|
private boolean peek = false;
|
|
private boolean closeFoldersOnStoreFailure = true;
|
|
private boolean enableCompress = false; // enable COMPRESS=DEFLATE
|
|
private boolean finalizeCleanClose = false;
|
|
|
|
/*
|
|
* This field is set in the Store's response handler if we see
|
|
* a BYE response. The releaseStore method checks this field
|
|
* and if set it cleans up the Store. Field is volatile because
|
|
* there's no lock we consistently hold while manipulating it.
|
|
*
|
|
* Because volatile doesn't really work before JDK 1.5,
|
|
* use a lock to protect these two fields.
|
|
*/
|
|
private volatile boolean connectionFailed = false;
|
|
private volatile boolean forceClose = false;
|
|
private final Object connectionFailedLock = new Object();
|
|
|
|
private boolean debugusername; // include username in debug output?
|
|
private boolean debugpassword; // include password in debug output?
|
|
protected MailLogger logger; // for debug output
|
|
|
|
private boolean messageCacheDebug;
|
|
|
|
// constructors for IMAPFolder class provided by user
|
|
private volatile Constructor<?> folderConstructor = null;
|
|
private volatile Constructor<?> folderConstructorLI = null;
|
|
|
|
// Connection pool info
|
|
|
|
static class ConnectionPool {
|
|
|
|
// container for the pool's IMAP protocol objects
|
|
private Vector<IMAPProtocol> authenticatedConnections
|
|
= new Vector<>();
|
|
|
|
// vectore of open folders
|
|
private Vector<IMAPFolder> folders;
|
|
|
|
// is the store connection being used?
|
|
private boolean storeConnectionInUse = false;
|
|
|
|
// the last time (in millis) the pool was checked for timed out
|
|
// connections
|
|
private long lastTimePruned;
|
|
|
|
// flag to indicate whether there is a dedicated connection for
|
|
// store commands
|
|
private final boolean separateStoreConnection;
|
|
|
|
// client timeout interval
|
|
private final long clientTimeoutInterval;
|
|
|
|
// server timeout interval
|
|
private final long serverTimeoutInterval;
|
|
|
|
// size of the connection pool
|
|
private final int poolSize;
|
|
|
|
// interval for checking for timed out connections
|
|
private final long pruningInterval;
|
|
|
|
// connection pool logger
|
|
private final MailLogger logger;
|
|
|
|
/*
|
|
* The idleState field supports the IDLE command.
|
|
* Normally when executing an IMAP command we hold the
|
|
* store's lock.
|
|
* While executing the IDLE command we can't hold the
|
|
* lock or it would prevent other threads from
|
|
* entering Store methods even far enough to check whether
|
|
* an IDLE command is in progress. We need to check before
|
|
* issuing another command so that we can abort the IDLE
|
|
* command.
|
|
*
|
|
* The idleState field is protected by the store's lock.
|
|
* The RUNNING state is the normal state and means no IDLE
|
|
* command is in progress. The IDLE state means we've issued
|
|
* an IDLE command and are reading responses. The ABORTING
|
|
* state means we've sent the DONE continuation command and
|
|
* are waiting for the thread running the IDLE command to
|
|
* break out of its read loop.
|
|
*
|
|
* When an IDLE command is in progress, the thread calling
|
|
* the idle method will be reading from the IMAP connection
|
|
* while not holding the store's lock.
|
|
* It's obviously critical that no other thread try to send a
|
|
* command or read from the connection while in this state.
|
|
* However, other threads can send the DONE continuation
|
|
* command that will cause the server to break out of the IDLE
|
|
* loop and send the ending tag response to the IDLE command.
|
|
* The thread in the idle method that's reading the responses
|
|
* from the IDLE command will see this ending response and
|
|
* complete the idle method, setting the idleState field back
|
|
* to RUNNING, and notifying any threads waiting to use the
|
|
* connection.
|
|
*
|
|
* All uses of the IMAP connection (IMAPProtocol object) must
|
|
* be preceeded by a check to make sure an IDLE command is not
|
|
* running, and abort the IDLE command if necessary. This check
|
|
* is made while holding the connection pool lock. While
|
|
* waiting for the IDLE command to complete, these other threads
|
|
* will give up the connection pool lock. This check is done by
|
|
* the getStoreProtocol() method.
|
|
*/
|
|
private static final int RUNNING = 0; // not doing IDLE command
|
|
private static final int IDLE = 1; // IDLE command in effect
|
|
private static final int ABORTING = 2; // IDLE command aborting
|
|
private int idleState = RUNNING;
|
|
private IMAPProtocol idleProtocol; // protocol object when IDLE
|
|
|
|
ConnectionPool(String name, MailLogger plogger, Session session) {
|
|
lastTimePruned = System.currentTimeMillis();
|
|
Properties props = session.getProperties();
|
|
|
|
boolean debug = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".connectionpool.debug", false);
|
|
logger = plogger.getSubLogger("connectionpool",
|
|
"DEBUG IMAP CP", debug);
|
|
|
|
// check if the default connection pool size is overridden
|
|
int size = PropUtil.getIntProperty(props,
|
|
"mail." + name + ".connectionpoolsize", -1);
|
|
if (size > 0) {
|
|
poolSize = size;
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.connectionpoolsize: " + poolSize);
|
|
} else
|
|
poolSize = 1;
|
|
|
|
// check if the default client-side timeout value is overridden
|
|
int connectionPoolTimeout = PropUtil.getIntProperty(props,
|
|
"mail." + name + ".connectionpooltimeout", -1);
|
|
if (connectionPoolTimeout > 0) {
|
|
clientTimeoutInterval = connectionPoolTimeout;
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.connectionpooltimeout: " +
|
|
clientTimeoutInterval);
|
|
} else
|
|
clientTimeoutInterval = 45 * 1000; // 45 seconds
|
|
|
|
// check if the default server-side timeout value is overridden
|
|
int serverTimeout = PropUtil.getIntProperty(props,
|
|
"mail." + name + ".servertimeout", -1);
|
|
if (serverTimeout > 0) {
|
|
serverTimeoutInterval = serverTimeout;
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.servertimeout: " +
|
|
serverTimeoutInterval);
|
|
} else
|
|
serverTimeoutInterval = 30 * 60 * 1000; // 30 minutes
|
|
|
|
// check if the default server-side timeout value is overridden
|
|
int pruning = PropUtil.getIntProperty(props,
|
|
"mail." + name + ".pruninginterval", -1);
|
|
if (pruning > 0) {
|
|
pruningInterval = pruning;
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.pruninginterval: " +
|
|
pruningInterval);
|
|
} else
|
|
pruningInterval = 60 * 1000; // 1 minute
|
|
|
|
// check to see if we should use a separate (i.e. dedicated)
|
|
// store connection
|
|
separateStoreConnection =
|
|
PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".separatestoreconnection", false);
|
|
if (separateStoreConnection)
|
|
logger.config("dedicate a store connection");
|
|
|
|
}
|
|
}
|
|
|
|
private final ConnectionPool pool;
|
|
|
|
/**
|
|
* A special response handler for connections that are being used
|
|
* to perform operations on behalf of an object other than the Store.
|
|
* It DOESN'T cause the Store to be cleaned up if a BYE is seen.
|
|
* The BYE may be real or synthetic and in either case just indicates
|
|
* that the connection is dead.
|
|
*/
|
|
private ResponseHandler nonStoreResponseHandler = new ResponseHandler() {
|
|
@Override
|
|
public void handleResponse(Response r) {
|
|
// Any of these responses may have a response code.
|
|
if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
|
|
handleResponseCode(r);
|
|
if (r.isBYE())
|
|
logger.fine("IMAPStore non-store connection dead");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Constructor that takes a Session object and a URLName that
|
|
* represents a specific IMAP server.
|
|
*
|
|
* @param session the Session
|
|
* @param url the URLName of this store
|
|
*/
|
|
public IMAPStore(Session session, URLName url) {
|
|
this(session, url, "imap", false);
|
|
}
|
|
|
|
/**
|
|
* Constructor used by this class and by IMAPSSLStore subclass.
|
|
*
|
|
* @param session the Session
|
|
* @param url the URLName of this store
|
|
* @param name the protocol name for this store
|
|
* @param isSSL use SSL?
|
|
*/
|
|
protected IMAPStore(Session session, URLName url,
|
|
String name, boolean isSSL) {
|
|
super(session, url); // call super constructor
|
|
Properties props = session.getProperties();
|
|
|
|
if (url != null)
|
|
name = url.getProtocol();
|
|
this.name = name;
|
|
if (!isSSL)
|
|
isSSL = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".ssl.enable", false);
|
|
if (isSSL)
|
|
this.defaultPort = 993;
|
|
else
|
|
this.defaultPort = 143;
|
|
this.isSSL = isSSL;
|
|
|
|
debug = session.getDebug();
|
|
debugusername = PropUtil.getBooleanProperty(props,
|
|
"mail.debug.auth.username", true);
|
|
debugpassword = PropUtil.getBooleanProperty(props,
|
|
"mail.debug.auth.password", false);
|
|
logger = new MailLogger(this.getClass(),
|
|
"DEBUG " + name.toUpperCase(Locale.ENGLISH),
|
|
session.getDebug(), session.getDebugOut());
|
|
|
|
boolean partialFetch = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".partialfetch", true);
|
|
if (!partialFetch) {
|
|
blksize = -1;
|
|
logger.config("mail.imap.partialfetch: false");
|
|
} else {
|
|
blksize = PropUtil.getIntProperty(props,
|
|
"mail." + name +".fetchsize", 1024 * 16);
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.fetchsize: " + blksize);
|
|
}
|
|
|
|
ignoreSize = PropUtil.getBooleanProperty(props,
|
|
"mail." + name +".ignorebodystructuresize", false);
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.ignorebodystructuresize: " + ignoreSize);
|
|
|
|
statusCacheTimeout = PropUtil.getIntProperty(props,
|
|
"mail." + name + ".statuscachetimeout", 1000);
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.statuscachetimeout: " +
|
|
statusCacheTimeout);
|
|
|
|
appendBufferSize = PropUtil.getIntProperty(props,
|
|
"mail." + name + ".appendbuffersize", -1);
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.appendbuffersize: " + appendBufferSize);
|
|
|
|
minIdleTime = PropUtil.getIntProperty(props,
|
|
"mail." + name + ".minidletime", 10);
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.minidletime: " + minIdleTime);
|
|
|
|
// check if we should do a PROXYAUTH login
|
|
String s = session.getProperty("mail." + name + ".proxyauth.user");
|
|
if (s != null) {
|
|
proxyAuthUser = s;
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("mail.imap.proxyauth.user: " + proxyAuthUser);
|
|
}
|
|
|
|
// check if STARTTLS is enabled
|
|
enableStartTLS = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".starttls.enable", false);
|
|
if (enableStartTLS)
|
|
logger.config("enable STARTTLS");
|
|
|
|
// check if STARTTLS is required
|
|
requireStartTLS = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".starttls.required", false);
|
|
if (requireStartTLS)
|
|
logger.config("require STARTTLS");
|
|
|
|
// check if SASL is enabled
|
|
enableSASL = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".sasl.enable", false);
|
|
if (enableSASL)
|
|
logger.config("enable SASL");
|
|
|
|
// check if SASL mechanisms are specified
|
|
if (enableSASL) {
|
|
s = session.getProperty("mail." + name + ".sasl.mechanisms");
|
|
if (s != null && s.length() > 0) {
|
|
if (logger.isLoggable(Level.CONFIG))
|
|
logger.config("SASL mechanisms allowed: " + s);
|
|
List<String> v = new ArrayList<>(5);
|
|
StringTokenizer st = new StringTokenizer(s, " ,");
|
|
while (st.hasMoreTokens()) {
|
|
String m = st.nextToken();
|
|
if (m.length() > 0)
|
|
v.add(m);
|
|
}
|
|
saslMechanisms = new String[v.size()];
|
|
v.toArray(saslMechanisms);
|
|
}
|
|
}
|
|
|
|
// check if an authorization ID has been specified
|
|
s = session.getProperty("mail." + name + ".sasl.authorizationid");
|
|
if (s != null) {
|
|
authorizationID = s;
|
|
logger.log(Level.CONFIG, "mail.imap.sasl.authorizationid: {0}",
|
|
authorizationID);
|
|
}
|
|
|
|
// check if a SASL realm has been specified
|
|
s = session.getProperty("mail." + name + ".sasl.realm");
|
|
if (s != null) {
|
|
saslRealm = s;
|
|
logger.log(Level.CONFIG, "mail.imap.sasl.realm: {0}", saslRealm);
|
|
}
|
|
|
|
// check if forcePasswordRefresh is enabled
|
|
forcePasswordRefresh = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".forcepasswordrefresh", false);
|
|
if (forcePasswordRefresh)
|
|
logger.config("enable forcePasswordRefresh");
|
|
|
|
// check if enableimapevents is enabled
|
|
enableResponseEvents = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".enableresponseevents", false);
|
|
if (enableResponseEvents)
|
|
logger.config("enable IMAP response events");
|
|
|
|
// check if enableresponseevents is enabled
|
|
enableImapEvents = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".enableimapevents", false);
|
|
if (enableImapEvents)
|
|
logger.config("enable IMAP IDLE events");
|
|
|
|
// check if message cache debugging set
|
|
messageCacheDebug = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".messagecache.debug", false);
|
|
|
|
guid = session.getProperty("mail." + name + ".yahoo.guid");
|
|
if (guid != null)
|
|
logger.log(Level.CONFIG, "mail.imap.yahoo.guid: {0}", guid);
|
|
|
|
// check if throwsearchexception is enabled
|
|
throwSearchException = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".throwsearchexception", false);
|
|
if (throwSearchException)
|
|
logger.config("throw SearchException");
|
|
|
|
// check if peek is set
|
|
peek = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".peek", false);
|
|
if (peek)
|
|
logger.config("peek");
|
|
|
|
// check if closeFoldersOnStoreFailure is set
|
|
closeFoldersOnStoreFailure = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".closefoldersonstorefailure", true);
|
|
if (closeFoldersOnStoreFailure)
|
|
logger.config("closeFoldersOnStoreFailure");
|
|
|
|
// check if COMPRESS is enabled
|
|
enableCompress = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".compress.enable", false);
|
|
if (enableCompress)
|
|
logger.config("enable COMPRESS");
|
|
|
|
// check if finalizeCleanClose is enabled
|
|
finalizeCleanClose = PropUtil.getBooleanProperty(props,
|
|
"mail." + name + ".finalizecleanclose", false);
|
|
if (finalizeCleanClose)
|
|
logger.config("close connection cleanly in finalize");
|
|
|
|
s = session.getProperty("mail." + name + ".folder.class");
|
|
if (s != null) {
|
|
logger.log(Level.CONFIG, "IMAP: folder class: {0}", s);
|
|
try {
|
|
ClassLoader cl = this.getClass().getClassLoader();
|
|
|
|
// now load the class
|
|
Class<?> folderClass = null;
|
|
try {
|
|
// First try the "application's" class loader.
|
|
// This should eventually be replaced by
|
|
// Thread.currentThread().getContextClassLoader().
|
|
folderClass = Class.forName(s, false, cl);
|
|
} catch (ClassNotFoundException ex1) {
|
|
// That didn't work, now try the "system" class loader.
|
|
// (Need both of these because JDK 1.1 class loaders
|
|
// may not delegate to their parent class loader.)
|
|
folderClass = Class.forName(s);
|
|
}
|
|
|
|
Class<?>[] c = { String.class, char.class, IMAPStore.class,
|
|
Boolean.class };
|
|
folderConstructor = folderClass.getConstructor(c);
|
|
Class<?>[] c2 = { ListInfo.class, IMAPStore.class };
|
|
folderConstructorLI = folderClass.getConstructor(c2);
|
|
} catch (Exception ex) {
|
|
logger.log(Level.CONFIG,
|
|
"IMAP: failed to load folder class", ex);
|
|
}
|
|
}
|
|
|
|
pool = new ConnectionPool(name, logger, session);
|
|
}
|
|
|
|
/**
|
|
* Implementation of protocolConnect(). Will create a connection
|
|
* to the server and authenticate the user using the mechanisms
|
|
* specified by various properties. <p>
|
|
*
|
|
* The <code>host</code>, <code>user</code>, and <code>password</code>
|
|
* parameters must all be non-null. If the authentication mechanism
|
|
* being used does not require a password, an empty string or other
|
|
* suitable dummy password should be used.
|
|
*/
|
|
@Override
|
|
protected synchronized boolean
|
|
protocolConnect(String host, int pport, String user, String password)
|
|
throws MessagingException {
|
|
|
|
IMAPProtocol protocol = null;
|
|
|
|
// check for non-null values of host, password, user
|
|
if (host == null || password == null || user == null) {
|
|
if (logger.isLoggable(Level.FINE))
|
|
logger.fine("protocolConnect returning false" +
|
|
", host=" + host +
|
|
", user=" + traceUser(user) +
|
|
", password=" + tracePassword(password));
|
|
return false;
|
|
}
|
|
|
|
// set the port correctly
|
|
if (pport != -1) {
|
|
port = pport;
|
|
} else {
|
|
port = PropUtil.getIntProperty(session.getProperties(),
|
|
"mail." + name + ".port", port);
|
|
}
|
|
|
|
// use the default if needed
|
|
if (port == -1) {
|
|
port = defaultPort;
|
|
}
|
|
|
|
try {
|
|
boolean poolEmpty;
|
|
synchronized (pool) {
|
|
poolEmpty = pool.authenticatedConnections.isEmpty();
|
|
}
|
|
|
|
if (poolEmpty) {
|
|
if (logger.isLoggable(Level.FINE))
|
|
logger.fine("trying to connect to host \"" + host +
|
|
"\", port " + port + ", isSSL " + isSSL);
|
|
protocol = newIMAPProtocol(host, port);
|
|
if (logger.isLoggable(Level.FINE))
|
|
logger.fine("protocolConnect login" +
|
|
", host=" + host +
|
|
", user=" + traceUser(user) +
|
|
", password=" + tracePassword(password));
|
|
protocol.addResponseHandler(nonStoreResponseHandler);
|
|
login(protocol, user, password);
|
|
protocol.removeResponseHandler(nonStoreResponseHandler);
|
|
protocol.addResponseHandler(this);
|
|
|
|
usingSSL = protocol.isSSL(); // in case anyone asks
|
|
|
|
this.host = host;
|
|
this.user = user;
|
|
this.password = password;
|
|
|
|
synchronized (pool) {
|
|
pool.authenticatedConnections.addElement(protocol);
|
|
}
|
|
}
|
|
} catch (IMAPReferralException ex) {
|
|
// login failure due to IMAP REFERRAL, close connection to server
|
|
if (protocol != null)
|
|
protocol.disconnect();
|
|
protocol = null;
|
|
throw new ReferralException(ex.getUrl(), ex.getMessage());
|
|
} catch (CommandFailedException cex) {
|
|
// login failure, close connection to server
|
|
if (protocol != null)
|
|
protocol.disconnect();
|
|
protocol = null;
|
|
Response r = cex.getResponse();
|
|
throw new AuthenticationFailedException(
|
|
r != null ? r.getRest() : cex.getMessage());
|
|
} catch (ProtocolException pex) { // any other exception
|
|
// failure in login command, close connection to server
|
|
if (protocol != null)
|
|
protocol.disconnect();
|
|
protocol = null;
|
|
throw new MessagingException(pex.getMessage(), pex);
|
|
} catch (SocketConnectException scex) {
|
|
throw new MailConnectException(scex);
|
|
} catch (IOException ioex) {
|
|
throw new MessagingException(ioex.getMessage(), ioex);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Create an IMAPProtocol object connected to the host and port.
|
|
* Subclasses of IMAPStore may override this method to return a
|
|
* subclass of IMAPProtocol that supports product-specific extensions.
|
|
*
|
|
* @param host the host name
|
|
* @param port the port number
|
|
* @return the new IMAPProtocol object
|
|
* @exception IOException for I/O errors
|
|
* @exception ProtocolException for protocol errors
|
|
* @since JavaMail 1.4.6
|
|
*/
|
|
protected IMAPProtocol newIMAPProtocol(String host, int port)
|
|
throws IOException, ProtocolException {
|
|
return new IMAPProtocol(name, host, port,
|
|
session.getProperties(),
|
|
isSSL,
|
|
logger
|
|
);
|
|
}
|
|
|
|
private void login(IMAPProtocol p, String u, String pw)
|
|
throws ProtocolException {
|
|
// turn on TLS if it's been enabled or required and is supported
|
|
// and we're not already using SSL
|
|
if ((enableStartTLS || requireStartTLS) && !p.isSSL()) {
|
|
if (p.hasCapability("STARTTLS")) {
|
|
p.startTLS();
|
|
// if startTLS succeeds, refresh capabilities
|
|
p.capability();
|
|
} else if (requireStartTLS) {
|
|
logger.fine("STARTTLS required but not supported by server");
|
|
throw new ProtocolException(
|
|
"STARTTLS required but not supported by server");
|
|
}
|
|
}
|
|
if (p.isAuthenticated())
|
|
return; // no need to login
|
|
|
|
// allow subclasses to issue commands before login
|
|
preLogin(p);
|
|
|
|
// issue special ID command to Yahoo! Mail IMAP server
|
|
// http://en.wikipedia.org/wiki/Yahoo%21_Mail#Free_IMAP_and_SMTPs_access
|
|
if (guid != null) {
|
|
Map<String,String> gmap = new HashMap<>();
|
|
gmap.put("GUID", guid);
|
|
p.id(gmap);
|
|
}
|
|
|
|
/*
|
|
* Put a special "marker" in the capabilities list so we can
|
|
* detect if the server refreshed the capabilities in the OK
|
|
* response.
|
|
*/
|
|
p.getCapabilities().put("__PRELOGIN__", "");
|
|
String authzid;
|
|
if (authorizationID != null)
|
|
authzid = authorizationID;
|
|
else if (proxyAuthUser != null)
|
|
authzid = proxyAuthUser;
|
|
else
|
|
authzid = null;
|
|
|
|
if (enableSASL) {
|
|
try {
|
|
p.sasllogin(saslMechanisms, saslRealm, authzid, u, pw);
|
|
if (!p.isAuthenticated())
|
|
throw new CommandFailedException(
|
|
"SASL authentication failed");
|
|
} catch (UnsupportedOperationException ex) {
|
|
// continue to try other authentication methods below
|
|
}
|
|
}
|
|
|
|
if (!p.isAuthenticated())
|
|
authenticate(p, authzid, u, pw);
|
|
|
|
if (proxyAuthUser != null)
|
|
p.proxyauth(proxyAuthUser);
|
|
|
|
/*
|
|
* If marker is still there, capabilities haven't been refreshed,
|
|
* refresh them now.
|
|
*/
|
|
if (p.hasCapability("__PRELOGIN__")) {
|
|
try {
|
|
p.capability();
|
|
} catch (ConnectionException cex) {
|
|
throw cex; // rethrow connection failures
|
|
// XXX - assume connection has been closed
|
|
} catch (ProtocolException pex) {
|
|
// ignore other exceptions that "should never happen"
|
|
}
|
|
}
|
|
|
|
if (enableCompress) {
|
|
if (p.hasCapability("COMPRESS=DEFLATE")) {
|
|
p.compress();
|
|
}
|
|
}
|
|
|
|
// if server supports UTF-8, enable it for client use
|
|
// note that this is safe to enable even if mail.mime.allowutf8=false
|
|
if (p.hasCapability("UTF8=ACCEPT") || p.hasCapability("UTF8=ONLY"))
|
|
p.enable("UTF8=ACCEPT");
|
|
}
|
|
|
|
/**
|
|
* Authenticate using one of the non-SASL mechanisms.
|
|
*
|
|
* @param p the IMAPProtocol object
|
|
* @param authzid the authorization ID
|
|
* @param user the user name
|
|
* @param password the password
|
|
* @exception ProtocolException on failures
|
|
*/
|
|
private void authenticate(IMAPProtocol p, String authzid,
|
|
String user, String password)
|
|
throws ProtocolException {
|
|
// this list must match the "if" statements below
|
|
String defaultAuthenticationMechanisms = "PLAIN LOGIN NTLM XOAUTH2";
|
|
|
|
// setting mail.imap.auth.mechanisms controls which mechanisms will
|
|
// be used, and in what order they'll be considered. only the first
|
|
// match is used.
|
|
String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
|
|
|
|
if (mechs == null)
|
|
mechs = defaultAuthenticationMechanisms;
|
|
|
|
/*
|
|
* Loop through the list of mechanisms supplied by the user
|
|
* (or defaulted) and try each in turn. If the server supports
|
|
* the mechanism and we have an authenticator for the mechanism,
|
|
* and it hasn't been disabled, use it.
|
|
*/
|
|
StringTokenizer st = new StringTokenizer(mechs);
|
|
while (st.hasMoreTokens()) {
|
|
String m = st.nextToken();
|
|
m = m.toUpperCase(Locale.ENGLISH);
|
|
|
|
/*
|
|
* If using the default mechanisms, check if this one is disabled.
|
|
*/
|
|
if (mechs == defaultAuthenticationMechanisms) {
|
|
String dprop = "mail." + name + ".auth." +
|
|
m.toLowerCase(Locale.ENGLISH) + ".disable";
|
|
boolean disabled = PropUtil.getBooleanProperty(
|
|
session.getProperties(),
|
|
dprop, m.equals("XOAUTH2"));
|
|
if (disabled) {
|
|
if (logger.isLoggable(Level.FINE))
|
|
logger.fine("mechanism " + m +
|
|
" disabled by property: " + dprop);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!(p.hasCapability("AUTH=" + m) ||
|
|
(m.equals("LOGIN") && p.hasCapability("AUTH-LOGIN")))) {
|
|
logger.log(Level.FINE, "mechanism {0} not supported by server",
|
|
m);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
if (m.equals("PLAIN"))
|
|
p.authplain(authzid, user, password);
|
|
else if (m.equals("LOGIN"))
|
|
p.authlogin(user, password);
|
|
else if (m.equals("NTLM"))
|
|
p.authntlm(authzid, user, password);
|
|
else if (m.equals("XOAUTH2"))
|
|
p.authoauth2(user, password);
|
|
else {
|
|
logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
|
|
continue;
|
|
}
|
|
} catch (ProtocolException ex) {
|
|
if (m.equals("PLAIN") || m.equals("LOGIN")) {
|
|
eu.faircode.email.Log.i("Falling back to classic LOGIN");
|
|
p.authclassic(user, password);
|
|
} else
|
|
throw ex;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!p.hasCapability("LOGINDISABLED")) {
|
|
p.login(user, password);
|
|
return;
|
|
}
|
|
|
|
throw new ProtocolException("No login methods supported!");
|
|
}
|
|
|
|
/**
|
|
* This method is called after the connection is made and
|
|
* TLS is started (if needed), but before any authentication
|
|
* is attempted. Subclasses can override this method to
|
|
* issue commands that are needed in the "not authenticated"
|
|
* state. Note that if the connection is pre-authenticated,
|
|
* this method won't be called. <p>
|
|
*
|
|
* The implementation of this method in this class does nothing.
|
|
*
|
|
* @param p the IMAPProtocol connection
|
|
* @exception ProtocolException for protocol errors
|
|
* @since JavaMail 1.4.4
|
|
*/
|
|
protected void preLogin(IMAPProtocol p) throws ProtocolException {
|
|
}
|
|
|
|
/**
|
|
* Does this IMAPStore use SSL when connecting to the server?
|
|
*
|
|
* @return true if using SSL
|
|
* @since JavaMail 1.4.6
|
|
*/
|
|
public synchronized boolean isSSL() {
|
|
return usingSSL;
|
|
}
|
|
|
|
/**
|
|
* Set the user name that will be used for subsequent connections
|
|
* after this Store is first connected (for example, when creating
|
|
* a connection to open a Folder). This value is overridden
|
|
* by any call to the Store's connect method. <p>
|
|
*
|
|
* Some IMAP servers may provide an authentication ID that can
|
|
* be used for more efficient authentication for future connections.
|
|
* This authentication ID is provided in a server-specific manner
|
|
* not described here. <p>
|
|
*
|
|
* Most applications will never need to use this method.
|
|
*
|
|
* @param user the user name for the store
|
|
* @since JavaMail 1.3.3
|
|
*/
|
|
public synchronized void setUsername(String user) {
|
|
this.user = user;
|
|
}
|
|
|
|
/**
|
|
* Set the password that will be used for subsequent connections
|
|
* after this Store is first connected (for example, when creating
|
|
* a connection to open a Folder). This value is overridden
|
|
* by any call to the Store's connect method. <p>
|
|
*
|
|
* Most applications will never need to use this method.
|
|
*
|
|
* @param password the password for the store
|
|
* @since JavaMail 1.3.3
|
|
*/
|
|
public synchronized void setPassword(String password) {
|
|
this.password = password;
|
|
}
|
|
|
|
/*
|
|
* Get a new authenticated protocol object for this Folder.
|
|
* Also store a reference to this folder in our list of
|
|
* open folders.
|
|
*/
|
|
IMAPProtocol getProtocol(IMAPFolder folder)
|
|
throws MessagingException {
|
|
IMAPProtocol p = null;
|
|
|
|
// keep looking for a connection until we get a good one
|
|
while (p == null) {
|
|
|
|
// New authenticated protocol objects are either acquired
|
|
// from the connection pool, or created when the pool is
|
|
// empty or no connections are available. None are available
|
|
// if the current pool size is one and the separate store
|
|
// property is set or the connection is in use.
|
|
|
|
synchronized (pool) {
|
|
|
|
// If there's none available in the pool,
|
|
// create a new one.
|
|
if (pool.authenticatedConnections.isEmpty() ||
|
|
(pool.authenticatedConnections.size() == 1 &&
|
|
(pool.separateStoreConnection || pool.storeConnectionInUse))) {
|
|
|
|
logger.fine("no connections in the pool, creating a new one");
|
|
try {
|
|
if (forcePasswordRefresh)
|
|
refreshPassword();
|
|
// Use cached host, port and timeout values.
|
|
p = newIMAPProtocol(host, port);
|
|
p.addResponseHandler(nonStoreResponseHandler);
|
|
// Use cached auth info
|
|
login(p, user, password);
|
|
p.removeResponseHandler(nonStoreResponseHandler);
|
|
} catch(Exception ex1) {
|
|
if (p != null)
|
|
try {
|
|
p.disconnect();
|
|
} catch (Exception ex2) { }
|
|
p = null;
|
|
eu.faircode.email.Log.e(new MessagingException("IMAP connection failure", ex1));
|
|
throw new MessagingException("connection failure", ex1);
|
|
}
|
|
|
|
if (p == null)
|
|
throw new MessagingException("connection failure");
|
|
} else {
|
|
if (logger.isLoggable(Level.FINE))
|
|
logger.fine("connection available -- size: " +
|
|
pool.authenticatedConnections.size());
|
|
|
|
// remove the available connection from the Authenticated queue
|
|
p = pool.authenticatedConnections.lastElement();
|
|
pool.authenticatedConnections.removeElement(p);
|
|
|
|
// check if the connection is still live
|
|
long lastUsed = System.currentTimeMillis() - p.getTimestamp();
|
|
if (lastUsed > pool.serverTimeoutInterval) {
|
|
try {
|
|
/*
|
|
* Swap in a special response handler that will handle
|
|
* alerts, but won't cause the store to be closed and
|
|
* cleaned up if the connection is dead.
|
|
*/
|
|
p.removeResponseHandler(this);
|
|
p.addResponseHandler(nonStoreResponseHandler);
|
|
p.noop();
|
|
p.removeResponseHandler(nonStoreResponseHandler);
|
|
p.addResponseHandler(this);
|
|
} catch (ProtocolException pex) {
|
|
try {
|
|
p.removeResponseHandler(nonStoreResponseHandler);
|
|
p.disconnect();
|
|
} catch (RuntimeException ignored) {
|
|
// don't let any exception stop us
|
|
}
|
|
p = null;
|
|
continue; // try again, from the top
|
|
}
|
|
}
|
|
|
|
// if proxyAuthUser has changed, switch to new user
|
|
if (proxyAuthUser != null &&
|
|
!proxyAuthUser.equals(p.getProxyAuthUser()) &&
|
|
p.hasCapability("X-UNAUTHENTICATE")) {
|
|
try {
|
|
/*
|
|
* Swap in a special response handler that will handle
|
|
* alerts, but won't cause the store to be closed and
|
|
* cleaned up if the connection is dead.
|
|
*/
|
|
p.removeResponseHandler(this);
|
|
p.addResponseHandler(nonStoreResponseHandler);
|
|
p.unauthenticate();
|
|
login(p, user, password);
|
|
p.removeResponseHandler(nonStoreResponseHandler);
|
|
p.addResponseHandler(this);
|
|
} catch (ProtocolException pex) {
|
|
try {
|
|
p.removeResponseHandler(nonStoreResponseHandler);
|
|
p.disconnect();
|
|
} catch (RuntimeException ignored) {
|
|
// don't let any exception stop us
|
|
}
|
|
p = null;
|
|
continue; // try again, from the top
|
|
}
|
|
}
|
|
|
|
// remove the store as a response handler.
|
|
p.removeResponseHandler(this);
|
|
}
|
|
|
|
// check if we need to look for client-side timeouts
|
|
timeoutConnections();
|
|
|
|
// Add folder to folder-list
|
|
if (folder != null) {
|
|
if (pool.folders == null)
|
|
pool.folders = new Vector<>();
|
|
pool.folders.addElement(folder);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Get this Store's protocol connection.
|
|
*
|
|
* When acquiring a store protocol object, it is important to
|
|
* use the following steps:
|
|
*
|
|
* IMAPProtocol p = null;
|
|
* try {
|
|
* p = getStoreProtocol();
|
|
* // perform the command
|
|
* } catch (ConnectionException cex) {
|
|
* throw new StoreClosedException(this, cex.getMessage());
|
|
* } catch (WhateverException ex) {
|
|
* // handle it
|
|
* } finally {
|
|
* releaseStoreProtocol(p);
|
|
* }
|
|
*/
|
|
private IMAPProtocol getStoreProtocol() throws ProtocolException {
|
|
IMAPProtocol p = null;
|
|
|
|
while (p == null) {
|
|
synchronized (pool) {
|
|
waitIfIdle();
|
|
|
|
// If there's no authenticated connections available create a
|
|
// new one and place it in the authenticated queue.
|
|
if (pool.authenticatedConnections.isEmpty()) {
|
|
pool.logger.fine("getStoreProtocol() - no connections " +
|
|
"in the pool, creating a new one");
|
|
try {
|
|
if (forcePasswordRefresh)
|
|
refreshPassword();
|
|
// Use cached host, port and timeout values.
|
|
p = newIMAPProtocol(host, port);
|
|
// Use cached auth info
|
|
login(p, user, password);
|
|
} catch(Exception ex1) {
|
|
if (p != null)
|
|
try {
|
|
p.logout();
|
|
} catch (Exception ex2) { }
|
|
p = null;
|
|
}
|
|
|
|
if (p == null)
|
|
throw new ConnectionException(
|
|
"failed to create new store connection");
|
|
|
|
p.addResponseHandler(this);
|
|
pool.authenticatedConnections.addElement(p);
|
|
|
|
} else {
|
|
// Always use the first element in the Authenticated queue.
|
|
if (pool.logger.isLoggable(Level.FINE))
|
|
pool.logger.fine("getStoreProtocol() - " +
|
|
"connection available -- size: " +
|
|
pool.authenticatedConnections.size());
|
|
p = pool.authenticatedConnections.firstElement();
|
|
|
|
// if proxyAuthUser has changed, switch to new user
|
|
if (proxyAuthUser != null &&
|
|
!proxyAuthUser.equals(p.getProxyAuthUser()) &&
|
|
p.hasCapability("X-UNAUTHENTICATE")) {
|
|
p.unauthenticate();
|
|
login(p, user, password);
|
|
}
|
|
}
|
|
|
|
if (pool.storeConnectionInUse) {
|
|
try {
|
|
// someone else is using the connection, give up
|
|
// and wait until they're done
|
|
p = null;
|
|
pool.wait();
|
|
} catch (InterruptedException ex) {
|
|
// restore the interrupted state, which callers might
|
|
// depend on
|
|
Thread.currentThread().interrupt();
|
|
// don't keep looking for a connection if we've been
|
|
// interrupted
|
|
throw new ProtocolException(
|
|
"Interrupted getStoreProtocol", ex);
|
|
}
|
|
} else {
|
|
pool.storeConnectionInUse = true;
|
|
|
|
pool.logger.fine("getStoreProtocol() -- storeConnectionInUse");
|
|
}
|
|
|
|
timeoutConnections();
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Get a store protocol object for use by a folder.
|
|
*/
|
|
IMAPProtocol getFolderStoreProtocol() throws ProtocolException {
|
|
IMAPProtocol p = getStoreProtocol();
|
|
p.removeResponseHandler(this);
|
|
p.addResponseHandler(nonStoreResponseHandler);
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Some authentication systems use one time passwords
|
|
* or tokens, so each authentication request requires
|
|
* a new password. This "kludge" allows a callback
|
|
* to application code to get a new password.
|
|
*
|
|
* XXX - remove this when SASL support is added
|
|
*/
|
|
private void refreshPassword() {
|
|
if (logger.isLoggable(Level.FINE))
|
|
logger.fine("refresh password, user: " + traceUser(user));
|
|
InetAddress addr;
|
|
try {
|
|
addr = InetAddress.getByName(host);
|
|
} catch (UnknownHostException e) {
|
|
addr = null;
|
|
}
|
|
PasswordAuthentication pa =
|
|
session.requestPasswordAuthentication(addr, port,
|
|
name, null, user);
|
|
if (pa != null) {
|
|
user = pa.getUserName();
|
|
password = pa.getPassword();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If a SELECT succeeds, but indicates that the folder is
|
|
* READ-ONLY, and the user asked to open the folder READ_WRITE,
|
|
* do we allow the open to succeed?
|
|
*/
|
|
boolean allowReadOnlySelect() {
|
|
return PropUtil.getBooleanProperty(session.getProperties(),
|
|
"mail." + name + ".allowreadonlyselect", false);
|
|
}
|
|
|
|
/**
|
|
* Report whether the separateStoreConnection is set.
|
|
*/
|
|
boolean hasSeparateStoreConnection() {
|
|
return pool.separateStoreConnection;
|
|
}
|
|
|
|
/**
|
|
* Return the connection pool logger.
|
|
*/
|
|
MailLogger getConnectionPoolLogger() {
|
|
return pool.logger;
|
|
}
|
|
|
|
/**
|
|
* Report whether message cache debugging is enabled.
|
|
*/
|
|
boolean getMessageCacheDebug() {
|
|
return messageCacheDebug;
|
|
}
|
|
|
|
/**
|
|
* Report whether the connection pool is full.
|
|
*/
|
|
boolean isConnectionPoolFull() {
|
|
|
|
synchronized (pool) {
|
|
if (pool.logger.isLoggable(Level.FINE))
|
|
pool.logger.fine("connection pool current size: " +
|
|
pool.authenticatedConnections.size() +
|
|
" pool size: " + pool.poolSize);
|
|
|
|
return (pool.authenticatedConnections.size() >= pool.poolSize);
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Release the protocol object back to the connection pool.
|
|
*/
|
|
void releaseProtocol(IMAPFolder folder, IMAPProtocol protocol) {
|
|
|
|
synchronized (pool) {
|
|
if (protocol != null) {
|
|
// If the pool is not full, add the store as a response handler
|
|
// and return the protocol object to the connection pool.
|
|
if (!isConnectionPoolFull()) {
|
|
protocol.addResponseHandler(this);
|
|
pool.authenticatedConnections.addElement(protocol);
|
|
|
|
if (logger.isLoggable(Level.FINE))
|
|
logger.fine(
|
|
"added an Authenticated connection -- size: " +
|
|
pool.authenticatedConnections.size());
|
|
} else {
|
|
logger.fine(
|
|
"pool is full, not adding an Authenticated connection");
|
|
try {
|
|
protocol.logout();
|
|
} catch (ProtocolException pex) {};
|
|
}
|
|
}
|
|
|
|
if (pool.folders != null)
|
|
pool.folders.removeElement(folder);
|
|
|
|
timeoutConnections();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Release the store connection.
|
|
*/
|
|
private void releaseStoreProtocol(IMAPProtocol protocol) {
|
|
|
|
// will be called from idle() without the Store lock held,
|
|
// but cleanup is synchronized and will acquire the Store lock
|
|
|
|
if (protocol == null) {
|
|
cleanup(); // failed to ever get the connection
|
|
return; // nothing to release
|
|
}
|
|
|
|
/*
|
|
* Read out the flag that says whether this connection failed
|
|
* before releasing the protocol object for others to use.
|
|
*/
|
|
boolean failed;
|
|
synchronized (connectionFailedLock) {
|
|
failed = connectionFailed;
|
|
connectionFailed = false; // reset for next use
|
|
}
|
|
|
|
// now free the store connection
|
|
synchronized (pool) {
|
|
pool.storeConnectionInUse = false;
|
|
pool.notifyAll(); // in case anyone waiting
|
|
|
|
pool.logger.fine("releaseStoreProtocol()");
|
|
|
|
timeoutConnections();
|
|
}
|
|
|
|
/*
|
|
* If the connection died while we were using it, clean up.
|
|
* It's critical that the store connection be freed and the
|
|
* connection pool not be locked while we do this.
|
|
*/
|
|
assert !Thread.holdsLock(pool);
|
|
if (failed)
|
|
cleanup();
|
|
}
|
|
|
|
/**
|
|
* Release a store protocol object that was being used by a folder.
|
|
*/
|
|
void releaseFolderStoreProtocol(IMAPProtocol protocol) {
|
|
if (protocol == null)
|
|
return; // should never happen
|
|
protocol.removeResponseHandler(nonStoreResponseHandler);
|
|
protocol.addResponseHandler(this);
|
|
synchronized (pool) {
|
|
pool.storeConnectionInUse = false;
|
|
pool.notifyAll(); // in case anyone waiting
|
|
|
|
pool.logger.fine("releaseFolderStoreProtocol()");
|
|
|
|
timeoutConnections();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Empty the connection pool.
|
|
*/
|
|
private void emptyConnectionPool(boolean force) {
|
|
|
|
synchronized (pool) {
|
|
for (int index = pool.authenticatedConnections.size() - 1;
|
|
index >= 0; --index) {
|
|
try {
|
|
IMAPProtocol p =
|
|
pool.authenticatedConnections.elementAt(index);
|
|
p.removeResponseHandler(this);
|
|
if (force)
|
|
p.disconnect();
|
|
else
|
|
p.logout();
|
|
} catch (ProtocolException pex) {};
|
|
}
|
|
|
|
pool.authenticatedConnections.removeAllElements();
|
|
}
|
|
|
|
pool.logger.fine("removed all authenticated connections from pool");
|
|
}
|
|
|
|
/**
|
|
* Check to see if it's time to shrink the connection pool.
|
|
*/
|
|
private void timeoutConnections() {
|
|
|
|
synchronized (pool) {
|
|
|
|
// If we've exceeded the pruning interval, look for stale
|
|
// connections to logout.
|
|
if (System.currentTimeMillis() - pool.lastTimePruned >
|
|
pool.pruningInterval &&
|
|
pool.authenticatedConnections.size() > 1) {
|
|
|
|
if (pool.logger.isLoggable(Level.FINE)) {
|
|
pool.logger.fine("checking for connections to prune: " +
|
|
(System.currentTimeMillis() - pool.lastTimePruned));
|
|
pool.logger.fine("clientTimeoutInterval: " +
|
|
pool.clientTimeoutInterval);
|
|
}
|
|
|
|
IMAPProtocol p;
|
|
|
|
// Check the timestamp of the protocol objects in the pool and
|
|
// logout if the interval exceeds the client timeout value
|
|
// (leave the first connection).
|
|
for (int index = pool.authenticatedConnections.size() - 1;
|
|
index > 0; index--) {
|
|
p = pool.authenticatedConnections.
|
|
elementAt(index);
|
|
if (pool.logger.isLoggable(Level.FINE))
|
|
pool.logger.fine("protocol last used: " +
|
|
(System.currentTimeMillis() - p.getTimestamp()));
|
|
if (System.currentTimeMillis() - p.getTimestamp() >
|
|
pool.clientTimeoutInterval) {
|
|
|
|
pool.logger.fine(
|
|
"authenticated connection timed out, " +
|
|
"logging out the connection");
|
|
|
|
p.removeResponseHandler(this);
|
|
pool.authenticatedConnections.removeElementAt(index);
|
|
|
|
try {
|
|
p.logout();
|
|
} catch (ProtocolException pex) {}
|
|
}
|
|
}
|
|
pool.lastTimePruned = System.currentTimeMillis();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the block size to use for fetch requests on this Store.
|
|
*/
|
|
int getFetchBlockSize() {
|
|
return blksize;
|
|
}
|
|
|
|
/**
|
|
* Ignore the size reported in the BODYSTRUCTURE when fetching data?
|
|
*/
|
|
boolean ignoreBodyStructureSize() {
|
|
return ignoreSize;
|
|
}
|
|
|
|
/**
|
|
* Get a reference to the session.
|
|
*/
|
|
Session getSession() {
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* Get the number of milliseconds to cache STATUS response.
|
|
*/
|
|
int getStatusCacheTimeout() {
|
|
return statusCacheTimeout;
|
|
}
|
|
|
|
/**
|
|
* Get the maximum size of a message to buffer for append.
|
|
*/
|
|
int getAppendBufferSize() {
|
|
return appendBufferSize;
|
|
}
|
|
|
|
/**
|
|
* Get the minimum amount of time to delay when returning from idle.
|
|
*/
|
|
int getMinIdleTime() {
|
|
return minIdleTime;
|
|
}
|
|
|
|
/**
|
|
* Throw a SearchException if the search expression is too complex?
|
|
*/
|
|
boolean throwSearchException() {
|
|
return throwSearchException;
|
|
}
|
|
|
|
/**
|
|
* Get the default "peek" value.
|
|
*/
|
|
boolean getPeek() {
|
|
return peek;
|
|
}
|
|
|
|
/**
|
|
* Return true if the specified capability string is in the list
|
|
* of capabilities the server announced.
|
|
*
|
|
* @param capability the capability string
|
|
* @return true if the server supports this capability
|
|
* @exception MessagingException for failures
|
|
* @since JavaMail 1.3.3
|
|
*/
|
|
public synchronized boolean hasCapability(String capability)
|
|
throws MessagingException {
|
|
IMAPProtocol p = null;
|
|
try {
|
|
p = getStoreProtocol();
|
|
return p.hasCapability(capability);
|
|
} catch (ProtocolException pex) {
|
|
throw new MessagingException(pex.getMessage(), pex);
|
|
} finally {
|
|
releaseStoreProtocol(p);
|
|
}
|
|
}
|
|
|
|
public synchronized String getCapability(String capability)
|
|
throws MessagingException {
|
|
IMAPProtocol p = null;
|
|
try {
|
|
p = getStoreProtocol();
|
|
Map<String, String> caps = p.getCapabilities();
|
|
if (caps != null)
|
|
for (String cap : caps.values()) {
|
|
int eq = (cap == null ? -1 : cap.indexOf('='));
|
|
if (eq > 0) {
|
|
String key = cap.substring(0, eq);
|
|
String value = cap.substring(eq + 1);
|
|
if (capability.equals(key))
|
|
return value;
|
|
}
|
|
}
|
|
return null;
|
|
} catch (ProtocolException pex) {
|
|
throw new MessagingException(pex.getMessage(), pex);
|
|
} finally {
|
|
releaseStoreProtocol(p);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the user name to be used with the PROXYAUTH command.
|
|
* The PROXYAUTH user name can also be set using the
|
|
* <code>mail.imap.proxyauth.user</code> property when this
|
|
* Store is created.
|
|
*
|
|
* @param user the user name to set
|
|
* @since JavaMail 1.5.1
|
|
*/
|
|
public void setProxyAuthUser(String user) {
|
|
proxyAuthUser = user;
|
|
}
|
|
|
|
/**
|
|
* Get the user name to be used with the PROXYAUTH command.
|
|
*
|
|
* @return the user name
|
|
* @since JavaMail 1.5.1
|
|
*/
|
|
public String getProxyAuthUser() {
|
|
return proxyAuthUser;
|
|
}
|
|
|
|
/**
|
|
* Check whether this store is connected. Override superclass
|
|
* method, to actually ping our server connection.
|
|
*/
|
|
@Override
|
|
public synchronized boolean isConnected() {
|
|
if (!super.isConnected()) {
|
|
// if we haven't been connected at all, don't bother with
|
|
// the NOOP.
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* The below noop() request can:
|
|
* (1) succeed - in which case all is fine.
|
|
*
|
|
* (2) fail because the server returns NO or BAD, in which
|
|
* case we ignore it since we can't really do anything.
|
|
* (2) fail because a BYE response is obtained from the
|
|
* server
|
|
* (3) fail because the socket.write() to the server fails,
|
|
* in which case the iap.protocol() code converts the
|
|
* IOException into a BYE response.
|
|
*
|
|
* Thus, our BYE handler will take care of closing the Store
|
|
* in case our connection is really gone.
|
|
*/
|
|
|
|
IMAPProtocol p = null;
|
|
try {
|
|
p = getStoreProtocol();
|
|
p.noop();
|
|
} catch (ProtocolException pex) {
|
|
// will return false below
|
|
} finally {
|
|
releaseStoreProtocol(p);
|
|
}
|
|
|
|
|
|
return super.isConnected();
|
|
}
|
|
|
|
/**
|
|
* Close this Store.
|
|
*/
|
|
@Override
|
|
public synchronized void close() throws MessagingException {
|
|
cleanup();
|
|
// do these again in case cleanup returned early
|
|
// because we were already closed due to a failure,
|
|
// in which case we force close everything
|
|
closeAllFolders(true);
|
|
emptyConnectionPool(true);
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
if (!finalizeCleanClose) {
|
|
// when finalizing, close connections abruptly
|
|
synchronized (connectionFailedLock) {
|
|
connectionFailed = true;
|
|
forceClose = true;
|
|
}
|
|
closeFoldersOnStoreFailure = true; // make sure folders get closed
|
|
}
|
|
try {
|
|
close();
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup before dying.
|
|
*/
|
|
private synchronized void cleanup() {
|
|
// if we're not connected, someone beat us to it
|
|
if (!super.isConnected()) {
|
|
logger.fine("IMAPStore cleanup, not connected");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If forceClose is true, some thread ran into an error that suggests
|
|
* the server might be dead, so we force the folders to close
|
|
* abruptly without waiting for the server. Used when
|
|
* the store connection times out, for example.
|
|
*/
|
|
boolean force;
|
|
synchronized (connectionFailedLock) {
|
|
force = forceClose;
|
|
forceClose = false;
|
|
connectionFailed = false;
|
|
}
|
|
if (logger.isLoggable(Level.FINE))
|
|
logger.fine("IMAPStore cleanup, force " + force);
|
|
|
|
if (!force || closeFoldersOnStoreFailure) {
|
|
closeAllFolders(force);
|
|
}
|
|
|
|
emptyConnectionPool(force);
|
|
|
|
// to set the state and send the closed connection event
|
|
try {
|
|
super.close();
|
|
} catch (MessagingException mex) {
|
|
// ignore it
|
|
}
|
|
logger.fine("IMAPStore cleanup done");
|
|
}
|
|
|
|
/**
|
|
* Close all open Folders. If force is true, close them forcibly.
|
|
*/
|
|
private void closeAllFolders(boolean force) {
|
|
List<IMAPFolder> foldersCopy = null;
|
|
boolean done = true;
|
|
|
|
// To avoid violating the locking hierarchy, there's no lock we
|
|
// can hold that prevents another thread from trying to open a
|
|
// folder at the same time we're trying to close all the folders.
|
|
// Thus, there's an inherent race condition here. We close all
|
|
// the folders we know about and then check whether any new folders
|
|
// have been opened in the mean time. We keep trying until we're
|
|
// successful in closing all the folders.
|
|
for (;;) {
|
|
// Make a copy of the folders list so we do not violate the
|
|
// folder-connection pool locking hierarchy.
|
|
synchronized (pool) {
|
|
if (pool.folders != null) {
|
|
done = false;
|
|
foldersCopy = pool.folders;
|
|
pool.folders = null;
|
|
} else {
|
|
done = true;
|
|
}
|
|
}
|
|
if (done)
|
|
break;
|
|
|
|
// Close and remove any open folders under this Store.
|
|
for (int i = 0, fsize = foldersCopy.size(); i < fsize; i++) {
|
|
IMAPFolder f = foldersCopy.get(i);
|
|
|
|
try {
|
|
if (force) {
|
|
logger.fine("force folder to close");
|
|
// Don't want to wait for folder connection to timeout
|
|
// (if, for example, the server is down) so we close
|
|
// folders abruptly.
|
|
f.forceClose();
|
|
} else {
|
|
logger.fine("close folder");
|
|
f.close(false);
|
|
}
|
|
} catch (MessagingException mex) {
|
|
// Who cares ?! Ignore 'em.
|
|
} catch (IllegalStateException ex) {
|
|
// Ditto
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the default folder, representing the root of this user's
|
|
* namespace. Returns a closed DefaultFolder object.
|
|
*/
|
|
@Override
|
|
public synchronized Folder getDefaultFolder() throws MessagingException {
|
|
checkConnected();
|
|
return new DefaultFolder(this);
|
|
}
|
|
|
|
/**
|
|
* Get named folder. Returns a new, closed IMAPFolder.
|
|
*/
|
|
@Override
|
|
public synchronized Folder getFolder(String name)
|
|
throws MessagingException {
|
|
checkConnected();
|
|
return newIMAPFolder(name, IMAPFolder.UNKNOWN_SEPARATOR);
|
|
}
|
|
|
|
/**
|
|
* Get named folder. Returns a new, closed IMAPFolder.
|
|
*/
|
|
@Override
|
|
public synchronized Folder getFolder(URLName url)
|
|
throws MessagingException {
|
|
checkConnected();
|
|
return newIMAPFolder(url.getFile(), IMAPFolder.UNKNOWN_SEPARATOR);
|
|
}
|
|
|
|
/**
|
|
* Create an IMAPFolder object. If user supplied their own class,
|
|
* use it. Otherwise, call the constructor.
|
|
*
|
|
* @param fullName the full name of the folder
|
|
* @param separator the separator character for the folder hierarchy
|
|
* @param isNamespace does this name represent a namespace?
|
|
* @return the new IMAPFolder object
|
|
*/
|
|
protected IMAPFolder newIMAPFolder(String fullName, char separator,
|
|
Boolean isNamespace) {
|
|
IMAPFolder f = null;
|
|
if (folderConstructor != null) {
|
|
try {
|
|
Object[] o =
|
|
{ fullName, Character.valueOf(separator), this, isNamespace };
|
|
f = (IMAPFolder)folderConstructor.newInstance(o);
|
|
} catch (Exception ex) {
|
|
logger.log(Level.FINE,
|
|
"exception creating IMAPFolder class", ex);
|
|
}
|
|
}
|
|
if (f == null)
|
|
f = new IMAPFolder(fullName, separator, this, isNamespace);
|
|
return f;
|
|
}
|
|
|
|
/**
|
|
* Create an IMAPFolder object. Call the newIMAPFolder method
|
|
* above with a null isNamespace.
|
|
*
|
|
* @param fullName the full name of the folder
|
|
* @param separator the separator character for the folder hierarchy
|
|
* @return the new IMAPFolder object
|
|
*/
|
|
protected IMAPFolder newIMAPFolder(String fullName, char separator) {
|
|
return newIMAPFolder(fullName, separator, null);
|
|
}
|
|
|
|
/**
|
|
* Create an IMAPFolder object. If user supplied their own class,
|
|
* use it. Otherwise, call the constructor.
|
|
*
|
|
* @param li the ListInfo for the folder
|
|
* @return the new IMAPFolder object
|
|
*/
|
|
protected IMAPFolder newIMAPFolder(ListInfo li) {
|
|
IMAPFolder f = null;
|
|
if (folderConstructorLI != null) {
|
|
try {
|
|
Object[] o = { li, this };
|
|
f = (IMAPFolder)folderConstructorLI.newInstance(o);
|
|
} catch (Exception ex) {
|
|
logger.log(Level.FINE,
|
|
"exception creating IMAPFolder class LI", ex);
|
|
}
|
|
}
|
|
if (f == null)
|
|
f = new IMAPFolder(li, this);
|
|
return f;
|
|
}
|
|
|
|
/**
|
|
* Using the IMAP NAMESPACE command (RFC 2342), return a set
|
|
* of folders representing the Personal namespaces.
|
|
*/
|
|
@Override
|
|
public Folder[] getPersonalNamespaces() throws MessagingException {
|
|
Namespaces ns = getNamespaces();
|
|
if (ns == null || ns.personal == null)
|
|
return super.getPersonalNamespaces();
|
|
return namespaceToFolders(ns.personal, null);
|
|
}
|
|
|
|
/**
|
|
* Using the IMAP NAMESPACE command (RFC 2342), return a set
|
|
* of folders representing the User's namespaces.
|
|
*/
|
|
@Override
|
|
public Folder[] getUserNamespaces(String user)
|
|
throws MessagingException {
|
|
Namespaces ns = getNamespaces();
|
|
if (ns == null || ns.otherUsers == null)
|
|
return super.getUserNamespaces(user);
|
|
return namespaceToFolders(ns.otherUsers, user);
|
|
}
|
|
|
|
/**
|
|
* Using the IMAP NAMESPACE command (RFC 2342), return a set
|
|
* of folders representing the Shared namespaces.
|
|
*/
|
|
@Override
|
|
public Folder[] getSharedNamespaces() throws MessagingException {
|
|
Namespaces ns = getNamespaces();
|
|
if (ns == null || ns.shared == null)
|
|
return super.getSharedNamespaces();
|
|
return namespaceToFolders(ns.shared, null);
|
|
}
|
|
|
|
private synchronized Namespaces getNamespaces() throws MessagingException {
|
|
checkConnected();
|
|
|
|
IMAPProtocol p = null;
|
|
|
|
if (namespaces == null) {
|
|
try {
|
|
p = getStoreProtocol();
|
|
namespaces = p.namespace();
|
|
} catch (BadCommandException bex) {
|
|
// NAMESPACE not supported, ignore it
|
|
} catch (ConnectionException cex) {
|
|
throw new StoreClosedException(this, cex.getMessage());
|
|
} catch (ProtocolException pex) {
|
|
throw new MessagingException(pex.getMessage(), pex);
|
|
} finally {
|
|
releaseStoreProtocol(p);
|
|
}
|
|
}
|
|
return namespaces;
|
|
}
|
|
|
|
private Folder[] namespaceToFolders(Namespaces.Namespace[] ns,
|
|
String user) {
|
|
Folder[] fa = new Folder[ns.length];
|
|
for (int i = 0; i < fa.length; i++) {
|
|
String name = ns[i].prefix;
|
|
if (user == null) {
|
|
// strip trailing delimiter
|
|
int len = name.length();
|
|
if ( len > 0 && name.charAt(len - 1) == ns[i].delimiter)
|
|
name = name.substring(0, len - 1);
|
|
} else {
|
|
// add user
|
|
name += user;
|
|
}
|
|
fa[i] = newIMAPFolder(name, ns[i].delimiter,
|
|
Boolean.valueOf(user == null));
|
|
}
|
|
return fa;
|
|
}
|
|
|
|
/**
|
|
* Get the quotas for the named quota root.
|
|
* Quotas are controlled on the basis of a quota root, not
|
|
* (necessarily) a folder. The relationship between folders
|
|
* and quota roots depends on the IMAP server. Some servers
|
|
* might implement a single quota root for all folders owned by
|
|
* a user. Other servers might implement a separate quota root
|
|
* for each folder. A single folder can even have multiple
|
|
* quota roots, perhaps controlling quotas for different
|
|
* resources.
|
|
*
|
|
* @param root the name of the quota root
|
|
* @return array of Quota objects
|
|
* @exception MessagingException if the server doesn't support the
|
|
* QUOTA extension
|
|
*/
|
|
@Override
|
|
public synchronized Quota[] getQuota(String root)
|
|
throws MessagingException {
|
|
checkConnected();
|
|
Quota[] qa = null;
|
|
|
|
IMAPProtocol p = null;
|
|
try {
|
|
p = getStoreProtocol();
|
|
qa = p.getQuotaRoot(root);
|
|
} catch (BadCommandException bex) {
|
|
throw new MessagingException("QUOTA not supported", bex);
|
|
} catch (ConnectionException cex) {
|
|
throw new StoreClosedException(this, cex.getMessage());
|
|
} catch (ProtocolException pex) {
|
|
throw new MessagingException(pex.getMessage(), pex);
|
|
} finally {
|
|
releaseStoreProtocol(p);
|
|
}
|
|
return qa;
|
|
}
|
|
|
|
/**
|
|
* Set the quotas for the quota root specified in the quota argument.
|
|
* Typically this will be one of the quota roots obtained from the
|
|
* <code>getQuota</code> method, but it need not be.
|
|
*
|
|
* @param quota the quota to set
|
|
* @exception MessagingException if the server doesn't support the
|
|
* QUOTA extension
|
|
*/
|
|
@Override
|
|
public synchronized void setQuota(Quota quota) throws MessagingException {
|
|
checkConnected();
|
|
IMAPProtocol p = null;
|
|
try {
|
|
p = getStoreProtocol();
|
|
p.setQuota(quota);
|
|
} catch (BadCommandException bex) {
|
|
throw new MessagingException("QUOTA not supported", bex);
|
|
} catch (ConnectionException cex) {
|
|
throw new StoreClosedException(this, cex.getMessage());
|
|
} catch (ProtocolException pex) {
|
|
throw new MessagingException(pex.getMessage(), pex);
|
|
} finally {
|
|
releaseStoreProtocol(p);
|
|
}
|
|
}
|
|
|
|
private void checkConnected() {
|
|
assert Thread.holdsLock(this);
|
|
if (!super.isConnected())
|
|
throw new IllegalStateException("Not connected");
|
|
}
|
|
|
|
/**
|
|
* Response handler method.
|
|
*/
|
|
@Override
|
|
public void handleResponse(Response r) {
|
|
// Any of these responses may have a response code.
|
|
if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
|
|
handleResponseCode(r);
|
|
if (r.isBYE()) {
|
|
logger.fine("IMAPStore connection dead");
|
|
// Store's IMAP connection is dead, save the response so that
|
|
// releaseStoreProtocol will cleanup later.
|
|
synchronized (connectionFailedLock) {
|
|
connectionFailed = true;
|
|
if (r.isSynthetic())
|
|
forceClose = true;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use the IMAP IDLE command (see
|
|
* <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>),
|
|
* if supported by the server, to enter idle mode so that the server
|
|
* can send unsolicited notifications
|
|
* without the need for the client to constantly poll the server.
|
|
* Use a <code>ConnectionListener</code> to be notified of
|
|
* events. When another thread (e.g., the listener thread)
|
|
* needs to issue an IMAP comand for this Store, the idle mode will
|
|
* be terminated and this method will return. Typically the caller
|
|
* will invoke this method in a loop. <p>
|
|
*
|
|
* If the mail.imap.enableimapevents property is set, notifications
|
|
* received while the IDLE command is active will be delivered to
|
|
* <code>ConnectionListener</code>s as events with a type of
|
|
* <code>IMAPStore.RESPONSE</code>. The event's message will be
|
|
* the raw IMAP response string.
|
|
* Note that most IMAP servers will not deliver any events when
|
|
* using the IDLE command on a connection with no mailbox selected
|
|
* (i.e., this method). In most cases you'll want to use the
|
|
* <code>idle</code> method on <code>IMAPFolder</code>. <p>
|
|
*
|
|
* NOTE: This capability is highly experimental and likely will change
|
|
* in future releases. <p>
|
|
*
|
|
* The mail.imap.minidletime property enforces a minimum delay
|
|
* before returning from this method, to ensure that other threads
|
|
* have a chance to issue commands before the caller invokes this
|
|
* method again. The default delay is 10 milliseconds.
|
|
*
|
|
* @exception MessagingException if the server doesn't support the
|
|
* IDLE extension
|
|
* @exception IllegalStateException if the store isn't connected
|
|
*
|
|
* @since JavaMail 1.4.1
|
|
*/
|
|
public void idle() throws MessagingException {
|
|
IMAPProtocol p = null;
|
|
// ASSERT: Must NOT be called with the connection pool
|
|
// synchronization lock held.
|
|
assert !Thread.holdsLock(pool);
|
|
synchronized (this) {
|
|
checkConnected();
|
|
}
|
|
boolean needNotification = false;
|
|
try {
|
|
synchronized (pool) {
|
|
p = getStoreProtocol();
|
|
if (pool.idleState != ConnectionPool.RUNNING) {
|
|
// some other thread must be running the IDLE
|
|
// command, we'll just wait for it to finish
|
|
// without aborting it ourselves
|
|
try {
|
|
// give up lock and wait to be not idle
|
|
pool.wait();
|
|
} catch (InterruptedException ex) {
|
|
// restore the interrupted state, which callers might
|
|
// depend on
|
|
Thread.currentThread().interrupt();
|
|
// stop waiting and return to caller
|
|
throw new MessagingException("idle interrupted", ex);
|
|
}
|
|
return;
|
|
}
|
|
p.idleStart();
|
|
needNotification = true;
|
|
pool.idleState = ConnectionPool.IDLE;
|
|
pool.idleProtocol = p;
|
|
}
|
|
|
|
/*
|
|
* We gave up the pool lock so that other threads
|
|
* can get into the pool far enough to see that we're
|
|
* in IDLE and abort the IDLE.
|
|
*
|
|
* Now we read responses from the IDLE command, especially
|
|
* including unsolicited notifications from the server.
|
|
* We don't hold the pool lock while reading because
|
|
* it protects the idleState and other threads need to be
|
|
* able to examine the state.
|
|
*
|
|
* We hold the pool lock while processing the responses.
|
|
*/
|
|
for (;;) {
|
|
Response r = p.readIdleResponse();
|
|
synchronized (pool) {
|
|
if (r == null || !p.processIdleResponse(r)) {
|
|
pool.idleState = ConnectionPool.RUNNING;
|
|
pool.idleProtocol = null;
|
|
pool.notifyAll();
|
|
needNotification = false;
|
|
break;
|
|
}
|
|
}
|
|
if (enableImapEvents && r.isUnTagged()) {
|
|
notifyStoreListeners(IMAPStore.RESPONSE, r.toString());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Enforce a minimum delay to give time to threads
|
|
* processing the responses that came in while we
|
|
* were idle.
|
|
*/
|
|
int minidle = getMinIdleTime();
|
|
if (minidle > 0) {
|
|
try {
|
|
Thread.sleep(minidle);
|
|
} catch (InterruptedException ex) {
|
|
// restore the interrupted state, which callers might
|
|
// depend on
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
}
|
|
|
|
} catch (BadCommandException bex) {
|
|
throw new MessagingException("IDLE not supported", bex);
|
|
} catch (ConnectionException cex) {
|
|
throw new StoreClosedException(this, cex.getMessage());
|
|
} catch (ProtocolException pex) {
|
|
throw new MessagingException(pex.getMessage(), pex);
|
|
} finally {
|
|
if (needNotification) {
|
|
synchronized (pool) {
|
|
pool.idleState = ConnectionPool.RUNNING;
|
|
pool.idleProtocol = null;
|
|
pool.notifyAll();
|
|
}
|
|
}
|
|
releaseStoreProtocol(p);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If an IDLE command is in progress, abort it if necessary,
|
|
* and wait until it completes.
|
|
* ASSERT: Must be called with the pool's lock held.
|
|
*/
|
|
private void waitIfIdle() throws ProtocolException {
|
|
assert Thread.holdsLock(pool);
|
|
while (pool.idleState != ConnectionPool.RUNNING) {
|
|
if (pool.idleState == ConnectionPool.IDLE) {
|
|
pool.idleProtocol.idleAbort();
|
|
pool.idleState = ConnectionPool.ABORTING;
|
|
}
|
|
try {
|
|
// give up lock and wait to be not idle
|
|
pool.wait();
|
|
} catch (InterruptedException ex) {
|
|
// If someone is trying to interrupt us we can't keep going
|
|
// around the loop waiting for IDLE to complete, but we can't
|
|
// just return because callers expect the idleState to be
|
|
// RUNNING when we return. Throwing this exception seems
|
|
// like the best choice.
|
|
throw new ProtocolException("Interrupted waitIfIdle", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send the IMAP ID command (if supported by the server) and return
|
|
* the result from the server. The ID command identfies the client
|
|
* to the server and returns information about the server to the client.
|
|
* See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
|
|
* The returned Map is unmodifiable.
|
|
*
|
|
* @param clientParams a Map of keys and values identifying the client
|
|
* @return a Map of keys and values identifying the server
|
|
* @exception MessagingException if the server doesn't support the
|
|
* ID extension
|
|
* @since JavaMail 1.5.1
|
|
*/
|
|
public synchronized Map<String, String> id(Map<String, String> clientParams)
|
|
throws MessagingException {
|
|
checkConnected();
|
|
Map<String, String> serverParams = null;
|
|
|
|
IMAPProtocol p = null;
|
|
try {
|
|
p = getStoreProtocol();
|
|
serverParams = p.id(clientParams);
|
|
} catch (BadCommandException bex) {
|
|
throw new MessagingException("ID not supported", bex);
|
|
} catch (ConnectionException cex) {
|
|
throw new StoreClosedException(this, cex.getMessage());
|
|
} catch (ProtocolException pex) {
|
|
throw new MessagingException(pex.getMessage(), pex);
|
|
} finally {
|
|
releaseStoreProtocol(p);
|
|
}
|
|
return serverParams;
|
|
}
|
|
|
|
/**
|
|
* Handle notifications and alerts.
|
|
* Response must be an OK, NO, BAD, or BYE response.
|
|
*/
|
|
void handleResponseCode(Response r) {
|
|
if (enableResponseEvents)
|
|
notifyStoreListeners(IMAPStore.RESPONSE, r.toString());
|
|
String s = r.getRest(); // get the text after the response
|
|
boolean isAlert = false;
|
|
if (s.startsWith("[")) { // a response code
|
|
int i = s.indexOf(']');
|
|
// remember if it's an alert
|
|
if (i > 0 && s.substring(0, i + 1).equalsIgnoreCase("[ALERT]"))
|
|
isAlert = true;
|
|
// strip off the response code in any event
|
|
s = s.substring(i + 1).trim();
|
|
}
|
|
if (isAlert)
|
|
notifyStoreListeners(StoreEvent.ALERT, s);
|
|
else if (r.isUnTagged() && s.length() > 0)
|
|
// Only send notifications that come with untagged
|
|
// responses, and only if there is actually some
|
|
// text there.
|
|
notifyStoreListeners(StoreEvent.NOTICE, s);
|
|
}
|
|
|
|
private String traceUser(String user) {
|
|
return debugusername ? user : "<user name suppressed>";
|
|
}
|
|
|
|
private String tracePassword(String password) {
|
|
return debugpassword ? password :
|
|
(password == null ? "<null>" : "<non-null>");
|
|
}
|
|
}
|