com.sun.mail.imap.protocol.IMAPProtocol Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2014 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.mail.imap.protocol;
import java.io.*;
import java.util.*;
import java.text.*;
import java.lang.reflect.*;
import java.util.logging.Level;
import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.search.*;
import com.sun.mail.util.*;
import com.sun.mail.iap.*;
import com.sun.mail.auth.Ntlm;
import com.sun.mail.imap.ACL;
import com.sun.mail.imap.Rights;
import com.sun.mail.imap.AppendUID;
import com.sun.mail.imap.CopyUID;
import com.sun.mail.imap.SortTerm;
import com.sun.mail.imap.ResyncData;
import com.sun.mail.imap.Utility;
/**
* This class extends the iap.Protocol object and implements IMAP
* semantics. In general, there is a method corresponding to each
* IMAP protocol command. The typical implementation issues the
* appropriate protocol command, collects all responses, processes
* those responses that are specific to this command and then
* dispatches the rest (the unsolicited ones) to the dispatcher
* using the notifyResponseHandlers(r)
.
*
* @author John Mani
* @author Bill Shannon
*/
public class IMAPProtocol extends Protocol {
private boolean connected = false; // did constructor succeed?
private boolean rev1 = false; // REV1 server ?
private boolean noauthdebug = true; // hide auth info in debug output
private boolean authenticated; // authenticated?
// WARNING: authenticated may be set to true in superclass
// constructor, don't initialize it here.
private Map capabilities;
// WARNING: capabilities may be initialized as a result of superclass
// constructor, don't initialize it here.
private List authmechs;
// WARNING: authmechs may be initialized as a result of superclass
// constructor, don't initialize it here.
protected SearchSequence searchSequence;
protected String[] searchCharsets; // array of search charsets
protected Set enabled; // enabled capabilities - RFC 5161
private String name;
private SaslAuthenticator saslAuthenticator; // if SASL is being used
private String proxyAuthUser; // user name used with PROXYAUTH
private ByteArray ba; // a buffer for fetchBody
private static final byte[] CRLF = { (byte)'\r', (byte)'\n'};
private static final FetchItem[] fetchItems = { };
/**
* Constructor.
* Opens a connection to the given host at given port.
*
* @param host host to connect to
* @param port portnumber to connect to
* @param debug debug mode
* @param props Properties object used by this protocol
*/
public IMAPProtocol(String name, String host, int port,
Properties props, boolean isSSL, MailLogger logger)
throws IOException, ProtocolException {
super(host, port, props, "mail." + name, isSSL, logger);
try {
this.name = name;
noauthdebug =
!PropUtil.getBooleanProperty(props, "mail.debug.auth", false);
if (capabilities == null)
capability();
if (hasCapability("IMAP4rev1"))
rev1 = true;
searchCharsets = new String[2]; // 2, for now.
searchCharsets[0] = "UTF-8";
searchCharsets[1] = MimeUtility.mimeCharset(
MimeUtility.getDefaultJavaCharset()
);
connected = true; // must be last statement in constructor
} finally {
/*
* If we get here because an exception was thrown, we need
* to disconnect to avoid leaving a connected socket that
* no one will be able to use because this object was never
* completely constructed.
*/
if (!connected)
disconnect();
}
}
/**
* Constructor for debugging.
*
* @param debug debug mode
* @param props Properties object used by this protocol
*/
public IMAPProtocol(InputStream in, PrintStream out,
Properties props, boolean debug)
throws IOException {
super(in, out, props, debug);
this.name = "imap";
noauthdebug =
!PropUtil.getBooleanProperty(props, "mail.debug.auth", false);
if (capabilities == null)
capabilities = new HashMap();
searchCharsets = new String[2]; // 2, for now.
searchCharsets[0] = "UTF-8";
searchCharsets[1] = MimeUtility.mimeCharset(
MimeUtility.getDefaultJavaCharset()
);
connected = true; // must be last statement in constructor
}
/**
* Return an array of FetchItem objects describing the
* FETCH items supported by this protocol. Subclasses may
* override this method to combine their FetchItems with
* the FetchItems returned by the superclass.
*
* @since JavaMail 1.4.6
*/
public FetchItem[] getFetchItems() {
return fetchItems;
}
/**
* CAPABILITY command.
*
* @see "RFC2060, section 6.1.1"
*/
public void capability() throws ProtocolException {
// Check CAPABILITY
Response[] r = command("CAPABILITY", null);
if (!r[r.length-1].isOK())
throw new ProtocolException(r[r.length-1].toString());
capabilities = new HashMap(10);
authmechs = new ArrayList(5);
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
// Handle *all* untagged CAPABILITY responses.
// Though the spec seemingly states that only
// one CAPABILITY response string is allowed (6.1.1),
// some server vendors claim otherwise.
if (ir.keyEquals("CAPABILITY"))
parseCapabilities(ir);
}
}
/**
* If the response contains a CAPABILITY response code, extract
* it and save the capabilities.
*/
protected void setCapabilities(Response r) {
byte b;
while ((b = r.readByte()) > 0 && b != (byte)'[')
;
if (b == 0)
return;
String s;
s = r.readAtom();
if (!s.equalsIgnoreCase("CAPABILITY"))
return;
capabilities = new HashMap(10);
authmechs = new ArrayList(5);
parseCapabilities(r);
}
/**
* Parse the capabilities from a CAPABILITY response or from
* a CAPABILITY response code attached to (e.g.) an OK response.
*/
protected void parseCapabilities(Response r) {
String s;
while ((s = r.readAtom()) != null) {
if (s.length() == 0) {
if (r.peekByte() == (byte)']')
break;
/*
* Probably found something here that's not an atom.
* Rather than loop forever or fail completely, we'll
* try to skip this bogus capability. This is known
* to happen with:
* Netscape Messaging Server 4.03 (built Apr 27 1999)
* that returns:
* * CAPABILITY * CAPABILITY IMAP4 IMAP4rev1 ...
* The "*" in the middle of the capability list causes
* us to loop forever here.
*/
r.skipToken();
} else {
capabilities.put(s.toUpperCase(Locale.ENGLISH), s);
if (s.regionMatches(true, 0, "AUTH=", 0, 5)) {
authmechs.add(s.substring(5));
if (logger.isLoggable(Level.FINE))
logger.fine("AUTH: " + s.substring(5));
}
}
}
}
/**
* Check the greeting when first connecting; look for PREAUTH response.
*/
protected void processGreeting(Response r) throws ProtocolException {
super.processGreeting(r); // check if it's BAD
if (r.isOK()) { // check if it's OK
setCapabilities(r);
return;
}
// only other choice is PREAUTH
assert r instanceof IMAPResponse;
IMAPResponse ir = (IMAPResponse)r;
if (ir.keyEquals("PREAUTH")) {
authenticated = true;
setCapabilities(r);
} else
throw new ConnectionException(this, r);
}
/**
* Returns true
if the connection has been authenticated,
* either due to a successful login, or due to a PREAUTH greeting response.
*/
public boolean isAuthenticated() {
return authenticated;
}
/**
* Returns true
if this is a IMAP4rev1 server
*/
public boolean isREV1() {
return rev1;
}
/**
* Returns whether this Protocol supports non-synchronizing literals.
*/
protected boolean supportsNonSyncLiterals() {
return hasCapability("LITERAL+");
}
/**
* Read a response from the server.
*/
public Response readResponse() throws IOException, ProtocolException {
// assert Thread.holdsLock(this);
// can't assert because it's called from constructor
IMAPResponse r = new IMAPResponse(this);
if (r.keyEquals("FETCH"))
r = new FetchResponse(r, getFetchItems());
return r;
}
/**
* Check whether the given capability is supported by
* this server. Returns true
if so, otherwise
* returns false.
*/
public boolean hasCapability(String c) {
if (c.endsWith("*")) {
c = c.substring(0, c.length() - 1).toUpperCase(Locale.ENGLISH);
Iterator it = capabilities.keySet().iterator();
while (it.hasNext()) {
if (((String)it.next()).startsWith(c))
return true;
}
return false;
}
return capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
}
/**
* Return the map of capabilities returned by the server.
*
* @since JavaMail 1.4.1
*/
public Map getCapabilities() {
return capabilities;
}
/**
* Close socket connection.
*
* This method just makes the Protocol.disconnect() method
* public.
*/
public void disconnect() {
super.disconnect();
authenticated = false; // just in case
}
/**
* The NOOP command.
*
* @see "RFC2060, section 6.1.2"
*/
public void noop() throws ProtocolException {
logger.fine("IMAPProtocol noop");
simpleCommand("NOOP", null);
}
/**
* LOGOUT Command.
*
* @see "RFC2060, section 6.1.3"
*/
public void logout() throws ProtocolException {
try {
Response[] r = command("LOGOUT", null);
authenticated = false;
// dispatch any unsolicited responses.
// NOTE that the BYE response is dispatched here as well
notifyResponseHandlers(r);
} finally {
disconnect();
}
}
/**
* LOGIN Command.
*
* @see "RFC2060, section 6.2.2"
*/
public void login(String u, String p) throws ProtocolException {
Argument args = new Argument();
args.writeString(u);
args.writeString(p);
Response[] r = null;
try {
if (noauthdebug && isTracing()) {
logger.fine("LOGIN command trace suppressed");
suspendTracing();
}
r = command("LOGIN", args);
} finally {
resumeTracing();
}
// dispatch untagged responses
notifyResponseHandlers(r);
// Handle result of this command
if (noauthdebug && isTracing())
logger.fine("LOGIN command result: " + r[r.length-1]);
handleResult(r[r.length-1]);
// If the response includes a CAPABILITY response code, process it
setCapabilities(r[r.length-1]);
// if we get this far without an exception, we're authenticated
authenticated = true;
}
/**
* The AUTHENTICATE command with AUTH=LOGIN authenticate scheme
*
* @see "RFC2060, section 6.2.1"
*/
public synchronized void authlogin(String u, String p)
throws ProtocolException {
List v = new ArrayList();
String tag = null;
Response r = null;
boolean done = false;
try {
if (noauthdebug && isTracing()) {
logger.fine("AUTHENTICATE LOGIN command trace suppressed");
suspendTracing();
}
try {
tag = writeCommand("AUTHENTICATE LOGIN", null);
} catch (Exception ex) {
// Convert this into a BYE response
r = Response.byeResponse(ex);
done = true;
}
OutputStream os = getOutputStream(); // stream to IMAP server
/* Wrap a BASE64Encoder around a ByteArrayOutputstream
* to craft b64 encoded username and password strings
*
* Note that the encoded bytes should be sent "as-is" to the
* server, *not* as literals or quoted-strings.
*
* Also note that unlike the B64 definition in MIME, CRLFs
* should *not* be inserted during the encoding process. So, I
* use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
* which should be sufficiently large !
*
* Finally, format the line in a buffer so it can be sent as
* a single packet, to avoid triggering a bug in SUN's SIMS 2.0
* server caused by patch 105346.
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
boolean first = true;
while (!done) { // loop till we are done
try {
r = readResponse();
if (r.isContinuation()) {
// Server challenge ..
String s;
if (first) { // Send encoded username
s = u;
first = false;
} else // Send encoded password
s = p;
// obtain b64 encoded bytes
b64os.write(ASCIIUtility.getBytes(s));
b64os.flush(); // complete the encoding
bos.write(CRLF); // CRLF termination
os.write(bos.toByteArray()); // write out line
os.flush(); // flush the stream
bos.reset(); // reset buffer
} else if (r.isTagged() && r.getTag().equals(tag))
// Ah, our tagged response
done = true;
else if (r.isBYE()) // outta here
done = true;
else // hmm .. unsolicited response here ?!
v.add(r);
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
}
} finally {
resumeTracing();
}
/* Dispatch untagged responses.
* NOTE: in our current upper level IMAP classes, we add the
* responseHandler to the Protocol object only *after* the
* connection has been authenticated. So, for now, the below
* code really ends up being just a no-op.
*/
Response[] responses = v.toArray(new Response[v.size()]);
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
if (noauthdebug && isTracing())
logger.fine("AUTHENTICATE LOGIN command result: " + r);
handleResult(r);
// If the response includes a CAPABILITY response code, process it
setCapabilities(r);
// if we get this far without an exception, we're authenticated
authenticated = true;
}
/**
* The AUTHENTICATE command with AUTH=PLAIN authentication scheme.
* This is based heavly on the {@link #authlogin} method.
*
* @param authzid the authorization id
* @param u the username
* @param p the password
* @throws ProtocolException as thrown by {@link Protocol#handleResult}.
* @see "RFC3501, section 6.2.2"
* @see "RFC2595, section 6"
* @since JavaMail 1.3.2
*/
public synchronized void authplain(String authzid, String u, String p)
throws ProtocolException {
List v = new ArrayList();
String tag = null;
Response r = null;
boolean done = false;
try {
if (noauthdebug && isTracing()) {
logger.fine("AUTHENTICATE PLAIN command trace suppressed");
suspendTracing();
}
try {
tag = writeCommand("AUTHENTICATE PLAIN", null);
} catch (Exception ex) {
// Convert this into a BYE response
r = Response.byeResponse(ex);
done = true;
}
OutputStream os = getOutputStream(); // stream to IMAP server
/* Wrap a BASE64Encoder around a ByteArrayOutputstream
* to craft b64 encoded username and password strings
*
* Note that the encoded bytes should be sent "as-is" to the
* server, *not* as literals or quoted-strings.
*
* Also note that unlike the B64 definition in MIME, CRLFs
* should *not* be inserted during the encoding process. So, I
* use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
* which should be sufficiently large !
*
* Finally, format the line in a buffer so it can be sent as
* a single packet, to avoid triggering a bug in SUN's SIMS 2.0
* server caused by patch 105346.
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
while (!done) { // loop till we are done
try {
r = readResponse();
if (r.isContinuation()) {
// Server challenge ..
final String nullByte = "\0";
String s = (authzid == null ? "" : authzid) +
nullByte + u + nullByte + p;
// obtain b64 encoded bytes
b64os.write(ASCIIUtility.getBytes(s));
b64os.flush(); // complete the encoding
bos.write(CRLF); // CRLF termination
os.write(bos.toByteArray()); // write out line
os.flush(); // flush the stream
bos.reset(); // reset buffer
} else if (r.isTagged() && r.getTag().equals(tag))
// Ah, our tagged response
done = true;
else if (r.isBYE()) // outta here
done = true;
else // hmm .. unsolicited response here ?!
v.add(r);
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
}
} finally {
resumeTracing();
}
/* Dispatch untagged responses.
* NOTE: in our current upper level IMAP classes, we add the
* responseHandler to the Protocol object only *after* the
* connection has been authenticated. So, for now, the below
* code really ends up being just a no-op.
*/
Response[] responses = v.toArray(new Response[v.size()]);
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
if (noauthdebug && isTracing())
logger.fine("AUTHENTICATE PLAIN command result: " + r);
handleResult(r);
// If the response includes a CAPABILITY response code, process it
setCapabilities(r);
// if we get this far without an exception, we're authenticated
authenticated = true;
}
/**
* The AUTHENTICATE command with AUTH=NTLM authentication scheme.
* This is based heavly on the {@link #authlogin} method.
*
* @param authzid the authorization id
* @param u the username
* @param p the password
* @throws ProtocolException as thrown by {@link Protocol#handleResult}.
* @see "RFC3501, section 6.2.2"
* @see "RFC2595, section 6"
* @since JavaMail 1.4.3
*/
public synchronized void authntlm(String authzid, String u, String p)
throws ProtocolException {
List v = new ArrayList();
String tag = null;
Response r = null;
boolean done = false;
String type1Msg = null;
int flags = PropUtil.getIntProperty(props,
"mail." + name + ".auth.ntlm.flags", 0);
String domain = props.getProperty(
"mail." + name + ".auth.ntlm.domain", "");
Ntlm ntlm = new Ntlm(domain, getLocalHost(), u, p, logger);
try {
if (noauthdebug && isTracing()) {
logger.fine("AUTHENTICATE NTLM command trace suppressed");
suspendTracing();
}
try {
tag = writeCommand("AUTHENTICATE NTLM", null);
} catch (Exception ex) {
// Convert this into a BYE response
r = Response.byeResponse(ex);
done = true;
}
OutputStream os = getOutputStream(); // stream to IMAP server
boolean first = true;
while (!done) { // loop till we are done
try {
r = readResponse();
if (r.isContinuation()) {
// Server challenge ..
String s;
if (first) {
s = ntlm.generateType1Msg(flags);
first = false;
} else {
s = ntlm.generateType3Msg(r.getRest());
}
os.write(ASCIIUtility.getBytes(s));
os.write(CRLF); // CRLF termination
os.flush(); // flush the stream
} else if (r.isTagged() && r.getTag().equals(tag))
// Ah, our tagged response
done = true;
else if (r.isBYE()) // outta here
done = true;
else // hmm .. unsolicited response here ?!
v.add(r);
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
}
} finally {
resumeTracing();
}
/*
* Dispatch untagged responses.
* NOTE: in our current upper level IMAP classes, we add the
* responseHandler to the Protocol object only *after* the
* connection has been authenticated. So, for now, the below
* code really ends up being just a no-op.
*/
Response[] responses = v.toArray(new Response[v.size()]);
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
if (noauthdebug && isTracing())
logger.fine("AUTHENTICATE NTLM command result: " + r);
handleResult(r);
// If the response includes a CAPABILITY response code, process it
setCapabilities(r);
// if we get this far without an exception, we're authenticated
authenticated = true;
}
/**
* SASL-based login.
*/
public void sasllogin(String[] allowed, String realm, String authzid,
String u, String p) throws ProtocolException {
boolean useCanonicalHostName = PropUtil.getBooleanProperty(props,
"mail." + name + ".sasl.usecanonicalhostname", false);
String serviceHost;
if (useCanonicalHostName)
serviceHost = getInetAddress().getCanonicalHostName();
else
serviceHost = host;
if (saslAuthenticator == null) {
try {
Class sac = Class.forName(
"com.sun.mail.imap.protocol.IMAPSaslAuthenticator");
Constructor c = sac.getConstructor(new Class[] {
IMAPProtocol.class,
String.class,
Properties.class,
MailLogger.class,
String.class
});
saslAuthenticator = (SaslAuthenticator)c.newInstance(
new Object[] {
this,
name,
props,
logger,
serviceHost
});
} catch (Exception ex) {
logger.log(Level.FINE, "Can't load SASL authenticator", ex);
// probably because we're running on a system without SASL
return; // not authenticated, try without SASL
}
}
// were any allowed mechanisms specified?
List v;
if (allowed != null && allowed.length > 0) {
// remove anything not supported by the server
v = new ArrayList(allowed.length);
for (int i = 0; i < allowed.length; i++)
if (authmechs.contains(allowed[i])) // XXX - case must match
v.add(allowed[i]);
} else {
// everything is allowed
v = authmechs;
}
String[] mechs = (String[])v.toArray(new String[v.size()]);
try {
if (noauthdebug && isTracing()) {
logger.fine("SASL authentication command trace suppressed");
suspendTracing();
}
if (saslAuthenticator.authenticate(mechs, realm, authzid, u, p)) {
if (noauthdebug && isTracing())
logger.fine("SASL authentication succeeded");
authenticated = true;
} else {
if (noauthdebug && isTracing())
logger.fine("SASL authentication failed");
}
} finally {
resumeTracing();
}
}
// XXX - for IMAPSaslAuthenticator access to protected method
OutputStream getIMAPOutputStream() {
return getOutputStream();
}
/**
* PROXYAUTH Command.
*
* @see "Netscape/iPlanet/SunONE Messaging Server extension"
*/
public void proxyauth(String u) throws ProtocolException {
Argument args = new Argument();
args.writeString(u);
simpleCommand("PROXYAUTH", args);
proxyAuthUser = u;
}
/**
* Get the user name used with the PROXYAUTH command.
* Returns null if PROXYAUTH was not used.
*
* @since JavaMail 1.5.1
*/
public String getProxyAuthUser() {
return proxyAuthUser;
}
/**
* UNAUTHENTICATE Command.
*
* @see "Netscape/iPlanet/SunONE Messaging Server extension"
* @since JavaMail 1.5.1
*/
public void unauthenticate() throws ProtocolException {
if (!hasCapability("X-UNAUTHENTICATE"))
throw new BadCommandException("UNAUTHENTICATE not supported");
simpleCommand("UNAUTHENTICATE", null);
authenticated = false;
}
/**
* ID Command, for Yahoo! Mail IMAP server.
*
* @deprecated As of JavaMail 1.5.1, replaced by
* {@link #id(Map)}
* @since JavaMail 1.4.4
*/
public void id(String guid) throws ProtocolException {
// support this for now, but remove it soon
Map gmap = new HashMap();
gmap.put("GUID", guid);
id(gmap);
}
/**
* STARTTLS Command.
*
* @see "RFC3501, section 6.2.1"
*/
public void startTLS() throws ProtocolException {
try {
super.startTLS("STARTTLS");
} catch (ProtocolException pex) {
logger.log(Level.FINE, "STARTTLS ProtocolException", pex);
// ProtocolException just means the command wasn't recognized,
// or failed. This should never happen if we check the
// CAPABILITY first.
throw pex;
} catch (Exception ex) {
logger.log(Level.FINE, "STARTTLS Exception", ex);
// any other exception means we have to shut down the connection
// generate an artificial BYE response and disconnect
Response[] r = { Response.byeResponse(ex) };
notifyResponseHandlers(r);
disconnect();
throw new ProtocolException("STARTTLS failure", ex);
}
}
/**
* SELECT Command.
*
* @see "RFC2060, section 6.3.1"
*/
public MailboxInfo select(String mbox) throws ProtocolException {
return select(mbox, null);
}
/**
* SELECT Command with QRESYNC data.
*
* @see "RFC2060, section 6.3.1"
* @see "RFC5162, section 3.1"
* @since JavaMail 1.5.1
*/
public MailboxInfo select(String mbox, ResyncData rd)
throws ProtocolException {
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
if (rd != null) {
if (rd == ResyncData.CONDSTORE) {
if (!hasCapability("CONDSTORE"))
throw new BadCommandException("CONDSTORE not supported");
args.writeArgument(new Argument().writeAtom("CONDSTORE"));
} else {
if (!hasCapability("QRESYNC"))
throw new BadCommandException("QRESYNC not supported");
args.writeArgument(resyncArgs(rd));
}
}
Response[] r = command("SELECT", args);
// Note that MailboxInfo also removes those responses
// it knows about
MailboxInfo minfo = new MailboxInfo(r);
// dispatch any remaining untagged responses
notifyResponseHandlers(r);
Response response = r[r.length-1];
if (response.isOK()) { // command succesful
if (response.toString().indexOf("READ-ONLY") != -1)
minfo.mode = Folder.READ_ONLY;
else
minfo.mode = Folder.READ_WRITE;
}
handleResult(response);
return minfo;
}
/**
* EXAMINE Command.
*
* @see "RFC2060, section 6.3.2"
*/
public MailboxInfo examine(String mbox) throws ProtocolException {
return examine(mbox, null);
}
/**
* EXAMINE Command with QRESYNC data.
*
* @see "RFC2060, section 6.3.2"
* @see "RFC5162, section 3.1"
* @since JavaMail 1.5.1
*/
public MailboxInfo examine(String mbox, ResyncData rd)
throws ProtocolException {
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
if (rd != null) {
if (rd == ResyncData.CONDSTORE) {
if (!hasCapability("CONDSTORE"))
throw new BadCommandException("CONDSTORE not supported");
args.writeArgument(new Argument().writeAtom("CONDSTORE"));
} else {
if (!hasCapability("QRESYNC"))
throw new BadCommandException("QRESYNC not supported");
args.writeArgument(resyncArgs(rd));
}
}
Response[] r = command("EXAMINE", args);
// Note that MailboxInfo also removes those responses
// it knows about
MailboxInfo minfo = new MailboxInfo(r);
minfo.mode = Folder.READ_ONLY; // Obviously
// dispatch any remaining untagged responses
notifyResponseHandlers(r);
handleResult(r[r.length-1]);
return minfo;
}
/**
* Generate a QRESYNC argument list based on the ResyncData.
*/
private static Argument resyncArgs(ResyncData rd) {
Argument cmd = new Argument();
cmd.writeAtom("QRESYNC");
Argument args = new Argument();
args.writeNumber(rd.getUIDValidity());
args.writeNumber(rd.getModSeq());
UIDSet[] uids = Utility.getResyncUIDSet(rd);
if (uids != null)
args.writeString(UIDSet.toString(uids));
cmd.writeArgument(args);
return cmd;
}
/**
* ENABLE Command.
*
* @see "RFC 5161"
* @since JavaMail 1.5.1
*/
public void enable(String cap) throws ProtocolException {
if (!hasCapability("ENABLE"))
throw new BadCommandException("ENABLE not supported");
Argument args = new Argument();
args.writeAtom(cap);
simpleCommand("ENABLE", args);
if (enabled == null)
enabled = new HashSet();
enabled.add(cap.toUpperCase(Locale.ENGLISH));
}
/**
* Is the capability/extension enabled?
*
* @see "RFC 5161"
* @since JavaMail 1.5.1
*/
public boolean isEnabled(String cap) {
if (enabled == null)
return false;
else
return enabled.contains(cap.toUpperCase(Locale.ENGLISH));
}
/**
* UNSELECT Command.
*
* @see "RFC 3691"
* @since JavaMail 1.4.4
*/
public void unselect() throws ProtocolException {
if (!hasCapability("UNSELECT"))
throw new BadCommandException("UNSELECT not supported");
simpleCommand("UNSELECT", null);
}
/**
* STATUS Command.
*
* @see "RFC2060, section 6.3.10"
*/
public Status status(String mbox, String[] items)
throws ProtocolException {
if (!isREV1() && !hasCapability("IMAP4SUNVERSION"))
// STATUS is rev1 only, however the non-rev1 SIMS2.0
// does support this.
throw new BadCommandException("STATUS not supported");
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
Argument itemArgs = new Argument();
if (items == null)
items = Status.standardItems;
for (int i = 0, len = items.length; i < len; i++)
itemArgs.writeAtom(items[i]);
args.writeArgument(itemArgs);
Response[] r = command("STATUS", args);
Status status = null;
Response response = r[r.length-1];
// Grab all STATUS responses
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("STATUS")) {
if (status == null)
status = new Status(ir);
else // collect 'em all
Status.add(status, new Status(ir));
r[i] = null;
}
}
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return status;
}
/**
* CREATE Command.
*
* @see "RFC2060, section 6.3.3"
*/
public void create(String mbox) throws ProtocolException {
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
simpleCommand("CREATE", args);
}
/**
* DELETE Command.
*
* @see "RFC2060, section 6.3.4"
*/
public void delete(String mbox) throws ProtocolException {
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
simpleCommand("DELETE", args);
}
/**
* RENAME Command.
*
* @see "RFC2060, section 6.3.5"
*/
public void rename(String o, String n) throws ProtocolException {
// encode the mbox as per RFC2060
o = BASE64MailboxEncoder.encode(o);
n = BASE64MailboxEncoder.encode(n);
Argument args = new Argument();
args.writeString(o);
args.writeString(n);
simpleCommand("RENAME", args);
}
/**
* SUBSCRIBE Command.
*
* @see "RFC2060, section 6.3.6"
*/
public void subscribe(String mbox) throws ProtocolException {
Argument args = new Argument();
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
args.writeString(mbox);
simpleCommand("SUBSCRIBE", args);
}
/**
* UNSUBSCRIBE Command.
*
* @see "RFC2060, section 6.3.7"
*/
public void unsubscribe(String mbox) throws ProtocolException {
Argument args = new Argument();
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
args.writeString(mbox);
simpleCommand("UNSUBSCRIBE", args);
}
/**
* LIST Command.
*
* @see "RFC2060, section 6.3.8"
*/
public ListInfo[] list(String ref, String pattern)
throws ProtocolException {
return doList("LIST", ref, pattern);
}
/**
* LSUB Command.
*
* @see "RFC2060, section 6.3.9"
*/
public ListInfo[] lsub(String ref, String pattern)
throws ProtocolException {
return doList("LSUB", ref, pattern);
}
/**
* Execute the specified LIST-like command (e.g., "LIST" or "LSUB"),
* using the reference and pattern.
*
* @since JavaMail 1.4.6
*/
protected ListInfo[] doList(String cmd, String ref, String pat)
throws ProtocolException {
// encode the mbox as per RFC2060
ref = BASE64MailboxEncoder.encode(ref);
pat = BASE64MailboxEncoder.encode(pat);
Argument args = new Argument();
args.writeString(ref);
args.writeString(pat);
Response[] r = command(cmd, args);
ListInfo[] linfo = null;
Response response = r[r.length-1];
if (response.isOK()) { // command succesful
List v = new ArrayList(1);
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals(cmd)) {
v.add(new ListInfo(ir));
r[i] = null;
}
}
if (v.size() > 0) {
linfo = v.toArray(new ListInfo[v.size()]);
}
}
// Dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return linfo;
}
/**
* APPEND Command.
*
* @see "RFC2060, section 6.3.11"
*/
public void append(String mbox, Flags f, Date d,
Literal data) throws ProtocolException {
appenduid(mbox, f, d, data, false); // ignore return value
}
/**
* APPEND Command, return uid from APPENDUID response code.
*
* @see "RFC2060, section 6.3.11"
*/
public AppendUID appenduid(String mbox, Flags f, Date d,
Literal data) throws ProtocolException {
return appenduid(mbox, f, d, data, true);
}
public AppendUID appenduid(String mbox, Flags f, Date d,
Literal data, boolean uid) throws ProtocolException {
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
if (f != null) { // set Flags in appended message
// can't set the \Recent flag in APPEND
if (f.contains(Flags.Flag.RECENT)) {
f = new Flags(f); // copy, don't modify orig
f.remove(Flags.Flag.RECENT); // remove RECENT from copy
}
/*
* HACK ALERT: We want the flag_list to be written out
* without any checking/processing of the bytes in it. If
* I use writeString(), the flag_list will end up being
* quoted since it contains "illegal" characters. So I
* am depending on implementation knowledge that writeAtom()
* does not do any checking/processing - it just writes out
* the bytes. What we really need is a writeFoo() that just
* dumps out its argument.
*/
args.writeAtom(createFlagList(f));
}
if (d != null) // set INTERNALDATE in appended message
args.writeString(INTERNALDATE.format(d));
args.writeBytes(data);
Response[] r = command("APPEND", args);
// dispatch untagged responses
notifyResponseHandlers(r);
// Handle result of this command
handleResult(r[r.length-1]);
if (uid)
return getAppendUID(r[r.length-1]);
else
return null;
}
/**
* If the response contains an APPENDUID response code, extract
* it and return an AppendUID object with the information.
*/
private AppendUID getAppendUID(Response r) {
if (!r.isOK())
return null;
byte b;
while ((b = r.readByte()) > 0 && b != (byte)'[')
;
if (b == 0)
return null;
String s;
s = r.readAtom();
if (!s.equalsIgnoreCase("APPENDUID"))
return null;
long uidvalidity = r.readLong();
long uid = r.readLong();
return new AppendUID(uidvalidity, uid);
}
/**
* CHECK Command.
*
* @see "RFC2060, section 6.4.1"
*/
public void check() throws ProtocolException {
simpleCommand("CHECK", null);
}
/**
* CLOSE Command.
*
* @see "RFC2060, section 6.4.2"
*/
public void close() throws ProtocolException {
simpleCommand("CLOSE", null);
}
/**
* EXPUNGE Command.
*
* @see "RFC2060, section 6.4.3"
*/
public void expunge() throws ProtocolException {
simpleCommand("EXPUNGE", null);
}
/**
* UID EXPUNGE Command.
*
* @see "RFC2359, section 4.1"
*/
public void uidexpunge(UIDSet[] set) throws ProtocolException {
if (!hasCapability("UIDPLUS"))
throw new BadCommandException("UID EXPUNGE not supported");
simpleCommand("UID EXPUNGE " + UIDSet.toString(set), null);
}
/**
* Fetch the BODYSTRUCTURE of the specified message.
*/
public BODYSTRUCTURE fetchBodyStructure(int msgno)
throws ProtocolException {
Response[] r = fetch(msgno, "BODYSTRUCTURE");
notifyResponseHandlers(r);
Response response = r[r.length-1];
if (response.isOK())
return FetchResponse.getItem(r, msgno, BODYSTRUCTURE.class);
else if (response.isNO())
return null;
else {
handleResult(response);
return null;
}
}
/**
* Fetch given BODY section, without marking the message
* as SEEN.
*/
public BODY peekBody(int msgno, String section)
throws ProtocolException {
return fetchBody(msgno, section, true);
}
/**
* Fetch given BODY section.
*/
public BODY fetchBody(int msgno, String section)
throws ProtocolException {
return fetchBody(msgno, section, false);
}
protected BODY fetchBody(int msgno, String section, boolean peek)
throws ProtocolException {
Response[] r;
if (section == null)
section = "";
String body = (peek ? "BODY.PEEK[" : "BODY[") + section + "]";
return fetchSectionBody(msgno, section, body);
}
/**
* Partial FETCH of given BODY section, without setting SEEN flag.
*/
public BODY peekBody(int msgno, String section, int start, int size)
throws ProtocolException {
return fetchBody(msgno, section, start, size, true, null);
}
/**
* Partial FETCH of given BODY section.
*/
public BODY fetchBody(int msgno, String section, int start, int size)
throws ProtocolException {
return fetchBody(msgno, section, start, size, false, null);
}
/**
* Partial FETCH of given BODY section, without setting SEEN flag.
*/
public BODY peekBody(int msgno, String section, int start, int size,
ByteArray ba) throws ProtocolException {
return fetchBody(msgno, section, start, size, true, ba);
}
/**
* Partial FETCH of given BODY section.
*/
public BODY fetchBody(int msgno, String section, int start, int size,
ByteArray ba) throws ProtocolException {
return fetchBody(msgno, section, start, size, false, ba);
}
protected BODY fetchBody(int msgno, String section, int start, int size,
boolean peek, ByteArray ba) throws ProtocolException {
this.ba = ba; // save for later use by getResponseBuffer
if (section == null)
section = "";
String body = (peek ? "BODY.PEEK[" : "BODY[") + section + "]<" +
String.valueOf(start) + "." +
String.valueOf(size) + ">";
return fetchSectionBody(msgno, section, body);
}
/**
* Fetch the given body section of the given message, using the
* body string "body".
*/
protected BODY fetchSectionBody(int msgno, String section, String body)
throws ProtocolException {
Response[] r;
r = fetch(msgno, body);
notifyResponseHandlers(r);
Response response = r[r.length-1];
if (response.isOK()) {
List bl = FetchResponse.getItems(r, msgno, BODY.class);
if (bl.size() == 1)
return bl.get(0); // the common case
if (logger.isLoggable(Level.FINEST))
logger.finest("got " + bl.size() +
" BODY responses for section " + section);
// more then one BODY response, have to find the right one
for (BODY br : bl) {
if (logger.isLoggable(Level.FINEST))
logger.finest("got BODY section " + br.getSection());
if (br.getSection().equalsIgnoreCase(section))
return br; // that's the one!
}
return null; // couldn't find it
} else if (response.isNO())
return null;
else {
handleResult(response);
return null;
}
}
/**
* Return a buffer to read a response into.
* The buffer is provided by fetchBody and is
* used only once.
*/
protected ByteArray getResponseBuffer() {
ByteArray ret = ba;
ba = null;
return ret;
}
/**
* Fetch the specified RFC822 Data item. 'what' names
* the item to be fetched. 'what' can be null
* to fetch the whole message.
*/
public RFC822DATA fetchRFC822(int msgno, String what)
throws ProtocolException {
Response[] r = fetch(msgno,
what == null ? "RFC822" : "RFC822." + what
);
// dispatch untagged responses
notifyResponseHandlers(r);
Response response = r[r.length-1];
if (response.isOK())
return FetchResponse.getItem(r, msgno, RFC822DATA.class);
else if (response.isNO())
return null;
else {
handleResult(response);
return null;
}
}
/**
* Fetch the FLAGS for the given message.
*/
public Flags fetchFlags(int msgno) throws ProtocolException {
Flags flags = null;
Response[] r = fetch(msgno, "FLAGS");
// Search for our FLAGS response
for (int i = 0, len = r.length; i < len; i++) {
if (r[i] == null ||
!(r[i] instanceof FetchResponse) ||
((FetchResponse)r[i]).getNumber() != msgno)
continue;
FetchResponse fr = (FetchResponse)r[i];
if ((flags = fr.getItem(FLAGS.class)) != null) {
r[i] = null; // remove this response
break;
}
}
// dispatch untagged responses
notifyResponseHandlers(r);
handleResult(r[r.length-1]);
return flags;
}
/**
* Fetch the IMAP UID for the given message.
*/
public UID fetchUID(int msgno) throws ProtocolException {
Response[] r = fetch(msgno, "UID");
// dispatch untagged responses
notifyResponseHandlers(r);
Response response = r[r.length-1];
if (response.isOK())
return FetchResponse.getItem(r, msgno, UID.class);
else if (response.isNO()) // XXX: Issue NOOP ?
return null;
else {
handleResult(response);
return null; // NOTREACHED
}
}
/**
* Fetch the IMAP MODSEQ for the given message.
*
* @since JavaMail 1.5.1
*/
public MODSEQ fetchMODSEQ(int msgno) throws ProtocolException {
Response[] r = fetch(msgno, "MODSEQ");
// dispatch untagged responses
notifyResponseHandlers(r);
Response response = r[r.length-1];
if (response.isOK())
return FetchResponse.getItem(r, msgno, MODSEQ.class);
else if (response.isNO()) // XXX: Issue NOOP ?
return null;
else {
handleResult(response);
return null; // NOTREACHED
}
}
/**
* Get the sequence number for the given UID. A UID object
* containing the sequence number is returned. If the given UID
* is invalid, null
is returned.
*/
public UID fetchSequenceNumber(long uid) throws ProtocolException {
UID u = null;
Response[] r = fetch(String.valueOf(uid), "UID", true);
for (int i = 0, len = r.length; i < len; i++) {
if (r[i] == null || !(r[i] instanceof FetchResponse))
continue;
FetchResponse fr = (FetchResponse)r[i];
if ((u = fr.getItem(UID.class)) != null) {
if (u.uid == uid) // this is the one we want
break;
else
u = null;
}
}
notifyResponseHandlers(r);
handleResult(r[r.length-1]);
return u;
}
/**
* Get the sequence numbers for UIDs ranging from start till end.
* UID objects that contain the sequence numbers are returned.
* If no UIDs in the given range are found, an empty array is returned.
*/
public UID[] fetchSequenceNumbers(long start, long end)
throws ProtocolException {
Response[] r = fetch(String.valueOf(start) + ":" +
(end == UIDFolder.LASTUID ? "*" :
String.valueOf(end)),
"UID", true);
UID u;
List v = new ArrayList();
for (int i = 0, len = r.length; i < len; i++) {
if (r[i] == null || !(r[i] instanceof FetchResponse))
continue;
FetchResponse fr = (FetchResponse)r[i];
if ((u = fr.getItem(UID.class)) != null)
v.add(u);
}
notifyResponseHandlers(r);
handleResult(r[r.length-1]);
return v.toArray(new UID[v.size()]);
}
/**
* Get the sequence numbers for UIDs specified in the array.
* UID objects that contain the sequence numbers are returned.
* If no UIDs in the given range are found, an empty array is returned.
*/
public UID[] fetchSequenceNumbers(long[] uids) throws ProtocolException {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < uids.length; i++) {
if (i > 0)
sb.append(",");
sb.append(String.valueOf(uids[i]));
}
Response[] r = fetch(sb.toString(), "UID", true);
UID u;
List v = new ArrayList();
for (int i = 0, len = r.length; i < len; i++) {
if (r[i] == null || !(r[i] instanceof FetchResponse))
continue;
FetchResponse fr = (FetchResponse)r[i];
if ((u = fr.getItem(UID.class)) != null)
v.add(u);
}
notifyResponseHandlers(r);
handleResult(r[r.length-1]);
return v.toArray(new UID[v.size()]);
}
/**
* Get the sequence numbers for messages changed since the given
* modseq and with UIDs ranging from start till end.
* Also, prefetch the flags for the returned messages.
*
* @see "RFC 4551"
* @since JavaMail 1.5.1
*/
public int[] uidfetchChangedSince(long start, long end, long modseq)
throws ProtocolException {
String msgSequence = String.valueOf(start) + ":" +
(end == UIDFolder.LASTUID ? "*" :
String.valueOf(end));
Response[] r = command("UID FETCH " + msgSequence +
" (FLAGS) (CHANGEDSINCE " + String.valueOf(modseq) + ")", null);
List v = new ArrayList();
for (int i = 0, len = r.length; i < len; i++) {
if (r[i] == null || !(r[i] instanceof FetchResponse))
continue;
FetchResponse fr = (FetchResponse)r[i];
v.add(Integer.valueOf(fr.getNumber()));
}
notifyResponseHandlers(r);
handleResult(r[r.length-1]);
// Copy the list into 'matches'
int vsize = v.size();
int[] matches = new int[vsize];
for (int i = 0; i < vsize; i++)
matches[i] = ((Integer)v.get(i)).intValue();
return matches;
}
public Response[] fetch(MessageSet[] msgsets, String what)
throws ProtocolException {
return fetch(MessageSet.toString(msgsets), what, false);
}
public Response[] fetch(int start, int end, String what)
throws ProtocolException {
return fetch(String.valueOf(start) + ":" + String.valueOf(end),
what, false);
}
public Response[] fetch(int msg, String what)
throws ProtocolException {
return fetch(String.valueOf(msg), what, false);
}
private Response[] fetch(String msgSequence, String what, boolean uid)
throws ProtocolException {
if (uid)
return command("UID FETCH " + msgSequence +" (" + what + ")",null);
else
return command("FETCH " + msgSequence + " (" + what + ")", null);
}
/**
* COPY command.
*/
public void copy(MessageSet[] msgsets, String mbox)
throws ProtocolException {
copyuid(MessageSet.toString(msgsets), mbox, false);
}
public void copy(int start, int end, String mbox)
throws ProtocolException {
copyuid(String.valueOf(start) + ":" + String.valueOf(end),
mbox, false);
}
/**
* COPY Command, return uid from COPYUID response code.
*
* @see "RFC2359, section 4.3"
*/
public CopyUID copyuid(MessageSet[] msgsets, String mbox)
throws ProtocolException {
return copyuid(MessageSet.toString(msgsets), mbox, true);
}
public CopyUID copyuid(int start, int end, String mbox)
throws ProtocolException {
return copyuid(String.valueOf(start) + ":" + String.valueOf(end),
mbox, true);
}
public CopyUID copyuid(String msgSequence, String mbox, boolean uid)
throws ProtocolException {
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeAtom(msgSequence);
args.writeString(mbox);
Response[] r = command("COPY", args);
// dispatch untagged responses
notifyResponseHandlers(r);
// Handle result of this command
handleResult(r[r.length-1]);
if (uid)
return getCopyUID(r[r.length-1]);
else
return null;
}
/**
* If the response contains a COPYUID response code, extract
* it and return a CopyUID object with the information.
*/
private CopyUID getCopyUID(Response r) {
if (!r.isOK())
return null;
byte b;
while ((b = r.readByte()) > 0 && b != (byte)'[')
;
if (b == 0)
return null;
String s;
s = r.readAtom();
if (!s.equalsIgnoreCase("COPYUID"))
return null;
long uidvalidity = r.readLong();
String src = r.readAtom();
String dst = r.readAtom();
return new CopyUID(uidvalidity,
UIDSet.parseUIDSets(src), UIDSet.parseUIDSets(dst));
}
public void storeFlags(MessageSet[] msgsets, Flags flags, boolean set)
throws ProtocolException {
storeFlags(MessageSet.toString(msgsets), flags, set);
}
public void storeFlags(int start, int end, Flags flags, boolean set)
throws ProtocolException {
storeFlags(String.valueOf(start) + ":" + String.valueOf(end),
flags, set);
}
/**
* Set the specified flags on this message.
*/
public void storeFlags(int msg, Flags flags, boolean set)
throws ProtocolException {
storeFlags(String.valueOf(msg), flags, set);
}
private void storeFlags(String msgset, Flags flags, boolean set)
throws ProtocolException {
Response[] r;
if (set)
r = command("STORE " + msgset + " +FLAGS " +
createFlagList(flags), null);
else
r = command("STORE " + msgset + " -FLAGS " +
createFlagList(flags), null);
// Dispatch untagged responses
notifyResponseHandlers(r);
handleResult(r[r.length-1]);
}
/**
* Creates an IMAP flag_list from the given Flags object.
*/
private String createFlagList(Flags flags) {
StringBuffer sb = new StringBuffer();
sb.append("("); // start of flag_list
Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
boolean first = true;
for (int i = 0; i < sf.length; i++) {
String s;
Flags.Flag f = sf[i];
if (f == Flags.Flag.ANSWERED)
s = "\\Answered";
else if (f == Flags.Flag.DELETED)
s = "\\Deleted";
else if (f == Flags.Flag.DRAFT)
s = "\\Draft";
else if (f == Flags.Flag.FLAGGED)
s = "\\Flagged";
else if (f == Flags.Flag.RECENT)
s = "\\Recent";
else if (f == Flags.Flag.SEEN)
s = "\\Seen";
else
continue; // skip it
if (first)
first = false;
else
sb.append(' ');
sb.append(s);
}
String[] uf = flags.getUserFlags(); // get the user flag strings
for (int i = 0; i < uf.length; i++) {
if (first)
first = false;
else
sb.append(' ');
sb.append(uf[i]);
}
sb.append(")"); // terminate flag_list
return sb.toString();
}
/**
* Issue the given search criterion on the specified message sets.
* Returns array of matching sequence numbers. An empty array
* is returned if no matches are found.
*
* @param msgsets array of MessageSets
* @param term SearchTerm
* @return array of matching sequence numbers.
*/
public int[] search(MessageSet[] msgsets, SearchTerm term)
throws ProtocolException, SearchException {
return search(MessageSet.toString(msgsets), term);
}
/**
* Issue the given search criterion on all messages in this folder.
* Returns array of matching sequence numbers. An empty array
* is returned if no matches are found.
*
* @param term SearchTerm
* @return array of matching sequence numbers.
*/
public int[] search(SearchTerm term)
throws ProtocolException, SearchException {
return search("ALL", term);
}
/*
* Apply the given SearchTerm on the specified sequence.
* Returns array of matching sequence numbers. Note that an empty
* array is returned for no matches.
*/
private int[] search(String msgSequence, SearchTerm term)
throws ProtocolException, SearchException {
// Check if the search "text" terms contain only ASCII chars
if (getSearchSequence().isAscii(term)) {
try {
return issueSearch(msgSequence, term, null);
} catch (IOException ioex) { /* will not happen */ }
}
/*
* The search "text" terms do contain non-ASCII chars. We need to
* use SEARCH CHARSET ...
* The charsets we try to use are UTF-8 and the locale's
* default charset. If the server supports UTF-8, great,
* always use it. Else we try to use the default charset.
*/
// Cycle thru the list of charsets
for (int i = 0; i < searchCharsets.length; i++) {
if (searchCharsets[i] == null)
continue;
try {
return issueSearch(msgSequence, term, searchCharsets[i]);
} catch (CommandFailedException cfx) {
/*
* Server returned NO. For now, I'll just assume that
* this indicates that this charset is unsupported.
* We can check the BADCHARSET response code once
* that's spec'd into the IMAP RFC ..
*/
searchCharsets[i] = null;
continue;
} catch (IOException ioex) {
/* Charset conversion failed. Try the next one */
continue;
} catch (ProtocolException pex) {
throw pex;
} catch (SearchException sex) {
throw sex;
}
}
// No luck.
throw new SearchException("Search failed");
}
/* Apply the given SearchTerm on the specified sequence, using the
* given charset.
* Returns array of matching sequence numbers. Note that an empty
* array is returned for no matches.
*/
private int[] issueSearch(String msgSequence, SearchTerm term,
String charset)
throws ProtocolException, SearchException, IOException {
// Generate a search-sequence with the given charset
Argument args = getSearchSequence().generateSequence(term,
charset == null ? null :
MimeUtility.javaCharset(charset)
);
args.writeAtom(msgSequence);
Response[] r;
if (charset == null) // text is all US-ASCII
r = command("SEARCH", args);
else
r = command("SEARCH CHARSET " + charset, args);
Response response = r[r.length-1];
int[] matches = null;
// Grab all SEARCH responses
if (response.isOK()) { // command succesful
List v = new ArrayList();
int num;
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
// There *will* be one SEARCH response.
if (ir.keyEquals("SEARCH")) {
while ((num = ir.readNumber()) != -1)
v.add(Integer.valueOf(num));
r[i] = null;
}
}
// Copy the vector into 'matches'
int vsize = v.size();
matches = new int[vsize];
for (int i = 0; i < vsize; i++)
matches[i] = v.get(i).intValue();
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return matches;
}
/**
* Get the SearchSequence object.
* The SearchSequence object instance is saved in the searchSequence
* field. Subclasses of IMAPProtocol may override this method to
* return a subclass of SearchSequence, in order to add support for
* product-specific search terms.
*
* @since JavaMail 1.4.6
*/
protected SearchSequence getSearchSequence() {
if (searchSequence == null)
searchSequence = new SearchSequence();
return searchSequence;
}
/**
* Sort messages in the folder according to the specified sort criteria.
* If the search term is not null, limit the sort to only the messages
* that match the search term.
* Returns an array of sorted sequence numbers. An empty array
* is returned if no matches are found.
*
* @param term sort criteria
* @param sterm SearchTerm
* @return array of matching sequence numbers.
*
* @see "RFC 5256"
* @since JavaMail 1.4.4
*/
public int[] sort(SortTerm[] term, SearchTerm sterm)
throws ProtocolException, SearchException {
if (!hasCapability("SORT*"))
throw new BadCommandException("SORT not supported");
if (term == null || term.length == 0)
throw new BadCommandException("Must have at least one sort term");
Argument args = new Argument();
Argument sargs = new Argument();
for (int i = 0; i < term.length; i++)
sargs.writeAtom(term[i].toString());
args.writeArgument(sargs); // sort criteria
args.writeAtom("UTF-8"); // charset specification
if (sterm != null) {
try {
args.append(
getSearchSequence().generateSequence(sterm, "UTF-8"));
} catch (IOException ioex) {
// should never happen
throw new SearchException(ioex.toString());
}
} else
args.writeAtom("ALL");
Response[] r = command("SORT", args);
Response response = r[r.length-1];
int[] matches = null;
// Grab all SORT responses
if (response.isOK()) { // command succesful
List v = new ArrayList();
int num;
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("SORT")) {
while ((num = ir.readNumber()) != -1)
v.add(Integer.valueOf(num));
r[i] = null;
}
}
// Copy the vector into 'matches'
int vsize = v.size();
matches = new int[vsize];
for (int i = 0; i < vsize; i++)
matches[i] = v.get(i).intValue();
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return matches;
}
/**
* NAMESPACE Command.
*
* @see "RFC2342"
*/
public Namespaces namespace() throws ProtocolException {
if (!hasCapability("NAMESPACE"))
throw new BadCommandException("NAMESPACE not supported");
Response[] r = command("NAMESPACE", null);
Namespaces namespace = null;
Response response = r[r.length-1];
// Grab NAMESPACE response
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("NAMESPACE")) {
if (namespace == null)
namespace = new Namespaces(ir);
r[i] = null;
}
}
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return namespace;
}
/**
* GETQUOTAROOT Command.
*
* Returns an array of Quota objects, representing the quotas
* for this mailbox and, indirectly, the quotaroots for this
* mailbox.
*
* @see "RFC2087"
*/
public Quota[] getQuotaRoot(String mbox) throws ProtocolException {
if (!hasCapability("QUOTA"))
throw new BadCommandException("GETQUOTAROOT not supported");
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
Response[] r = command("GETQUOTAROOT", args);
Response response = r[r.length-1];
Hashtable tab = new Hashtable();
// Grab all QUOTAROOT and QUOTA responses
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("QUOTAROOT")) {
// quotaroot_response
// ::= "QUOTAROOT" SP astring *(SP astring)
// read name of mailbox and throw away
ir.readAtomString();
// for each quotaroot add a placeholder quota
String root = null;
while ((root = ir.readAtomString()) != null &&
root.length() > 0)
tab.put(root, new Quota(root));
r[i] = null;
} else if (ir.keyEquals("QUOTA")) {
Quota quota = parseQuota(ir);
Quota q = (Quota)tab.get(quota.quotaRoot);
if (q != null && q.resources != null) {
// merge resources
int newl = q.resources.length + quota.resources.length;
Quota.Resource[] newr = new Quota.Resource[newl];
System.arraycopy(q.resources, 0, newr, 0,
q.resources.length);
System.arraycopy(quota.resources, 0,
newr, q.resources.length, quota.resources.length);
quota.resources = newr;
}
tab.put(quota.quotaRoot, quota);
r[i] = null;
}
}
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
Quota[] qa = new Quota[tab.size()];
Enumeration e = tab.elements();
for (int i = 0; e.hasMoreElements(); i++)
qa[i] = (Quota)e.nextElement();
return qa;
}
/**
* GETQUOTA Command.
*
* Returns an array of Quota objects, representing the quotas
* for this quotaroot.
*
* @see "RFC2087"
*/
public Quota[] getQuota(String root) throws ProtocolException {
if (!hasCapability("QUOTA"))
throw new BadCommandException("QUOTA not supported");
Argument args = new Argument();
args.writeString(root);
Response[] r = command("GETQUOTA", args);
Quota quota = null;
List v = new ArrayList();
Response response = r[r.length-1];
// Grab all QUOTA responses
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("QUOTA")) {
quota = parseQuota(ir);
v.add(quota);
r[i] = null;
}
}
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return v.toArray(new Quota[v.size()]);
}
/**
* SETQUOTA Command.
*
* Set the indicated quota on the corresponding quotaroot.
*
* @see "RFC2087"
*/
public void setQuota(Quota quota) throws ProtocolException {
if (!hasCapability("QUOTA"))
throw new BadCommandException("QUOTA not supported");
Argument args = new Argument();
args.writeString(quota.quotaRoot);
Argument qargs = new Argument();
if (quota.resources != null) {
for (int i = 0; i < quota.resources.length; i++) {
qargs.writeAtom(quota.resources[i].name);
qargs.writeNumber(quota.resources[i].limit);
}
}
args.writeArgument(qargs);
Response[] r = command("SETQUOTA", args);
Response response = r[r.length-1];
// XXX - It's not clear from the RFC whether the SETQUOTA command
// will provoke untagged QUOTA responses. If it does, perhaps
// we should grab them here and return them?
/*
Quota quota = null;
List v = new ArrayList();
// Grab all QUOTA responses
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("QUOTA")) {
quota = parseQuota(ir);
v.add(quota);
r[i] = null;
}
}
}
*/
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
/*
return v.toArray(new Quota[v.size()]);
*/
}
/**
* Parse a QUOTA response.
*/
private Quota parseQuota(Response r) throws ParsingException {
// quota_response ::= "QUOTA" SP astring SP quota_list
String quotaRoot = r.readAtomString(); // quotaroot ::= astring
Quota q = new Quota(quotaRoot);
r.skipSpaces();
// quota_list ::= "(" #quota_resource ")"
if (r.readByte() != '(')
throw new ParsingException("parse error in QUOTA");
List v = new ArrayList();
while (r.peekByte() != ')') {
// quota_resource ::= atom SP number SP number
String name = r.readAtom();
if (name != null) {
long usage = r.readLong();
long limit = r.readLong();
Quota.Resource res = new Quota.Resource(name, usage, limit);
v.add(res);
}
}
r.readByte();
q.resources = v.toArray(new Quota.Resource[v.size()]);
return q;
}
/**
* SETACL Command.
*
* @see "RFC2086"
*/
public void setACL(String mbox, char modifier, ACL acl)
throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
args.writeString(acl.getName());
String rights = acl.getRights().toString();
if (modifier == '+' || modifier == '-')
rights = modifier + rights;
args.writeString(rights);
Response[] r = command("SETACL", args);
Response response = r[r.length-1];
// dispatch untagged responses
notifyResponseHandlers(r);
handleResult(response);
}
/**
* DELETEACL Command.
*
* @see "RFC2086"
*/
public void deleteACL(String mbox, String user) throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
args.writeString(user);
Response[] r = command("DELETEACL", args);
Response response = r[r.length-1];
// dispatch untagged responses
notifyResponseHandlers(r);
handleResult(response);
}
/**
* GETACL Command.
*
* @see "RFC2086"
*/
public ACL[] getACL(String mbox) throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
Response[] r = command("GETACL", args);
Response response = r[r.length-1];
// Grab all ACL responses
List v = new ArrayList();
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("ACL")) {
// acl_data ::= "ACL" SPACE mailbox
// *(SPACE identifier SPACE rights)
// read name of mailbox and throw away
ir.readAtomString();
String name = null;
while ((name = ir.readAtomString()) != null) {
String rights = ir.readAtomString();
if (rights == null)
break;
ACL acl = new ACL(name, new Rights(rights));
v.add(acl);
}
r[i] = null;
}
}
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return v.toArray(new ACL[v.size()]);
}
/**
* LISTRIGHTS Command.
*
* @see "RFC2086"
*/
public Rights[] listRights(String mbox, String user)
throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
args.writeString(user);
Response[] r = command("LISTRIGHTS", args);
Response response = r[r.length-1];
// Grab LISTRIGHTS response
List v = new ArrayList();
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("LISTRIGHTS")) {
// listrights_data ::= "LISTRIGHTS" SPACE mailbox
// SPACE identifier SPACE rights *(SPACE rights)
// read name of mailbox and throw away
ir.readAtomString();
// read identifier and throw away
ir.readAtomString();
String rights;
while ((rights = ir.readAtomString()) != null)
v.add(new Rights(rights));
r[i] = null;
}
}
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return v.toArray(new Rights[v.size()]);
}
/**
* MYRIGHTS Command.
*
* @see "RFC2086"
*/
public Rights myRights(String mbox) throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
// encode the mbox as per RFC2060
mbox = BASE64MailboxEncoder.encode(mbox);
Argument args = new Argument();
args.writeString(mbox);
Response[] r = command("MYRIGHTS", args);
Response response = r[r.length-1];
// Grab MYRIGHTS response
Rights rights = null;
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("MYRIGHTS")) {
// myrights_data ::= "MYRIGHTS" SPACE mailbox SPACE rights
// read name of mailbox and throw away
ir.readAtomString();
String rs = ir.readAtomString();
if (rights == null)
rights = new Rights(rs);
r[i] = null;
}
}
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return rights;
}
/*
* The tag used on the IDLE command. Set by idleStart() and
* used in processIdleResponse() to determine if the response
* is the matching end tag.
*/
private volatile String idleTag;
/**
* IDLE Command.
*
* If the server supports the IDLE command extension, the IDLE
* command is issued and this method blocks until a response has
* been received. Once the first response has been received, the
* IDLE command is terminated and all responses are collected and
* handled and this method returns.
*
* Note that while this method is blocked waiting for a response,
* no other threads may issue any commands to the server that would
* use this same connection.
*
* @see "RFC2177"
* @since JavaMail 1.4.1
*/
public synchronized void idleStart() throws ProtocolException {
if (!hasCapability("IDLE"))
throw new BadCommandException("IDLE not supported");
List v = new ArrayList();
boolean done = false;
Response r = null;
// write the command
try {
idleTag = writeCommand("IDLE", null);
} catch (LiteralException lex) {
v.add(lex.getResponse());
done = true;
} catch (Exception ex) {
// Convert this into a BYE response
v.add(Response.byeResponse(ex));
done = true;
}
while (!done) {
try {
r = readResponse();
} catch (IOException ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
} catch (ProtocolException pex) {
continue; // skip this response
}
v.add(r);
if (r.isContinuation() || r.isBYE())
done = true;
}
Response[] responses = v.toArray(new Response[v.size()]);
r = responses[responses.length-1];
// dispatch remaining untagged responses
notifyResponseHandlers(responses);
if (!r.isContinuation())
handleResult(r);
}
/**
* While an IDLE command is in progress, read a response
* sent from the server. The response is read with no locks
* held so that when the read blocks waiting for the response
* from the server it's not holding locks that would prevent
* other threads from interrupting the IDLE command.
*
* @since JavaMail 1.4.1
*/
public synchronized Response readIdleResponse() {
if (idleTag == null)
return null; // IDLE not in progress
Response r = null;
while (r == null) {
try {
r = readResponse();
} catch (InterruptedIOException iioex) {
/*
* If a socket timeout was set, the read will timeout
* before the IDLE times out. In that case, just go
* back and read some more. After all, the point of
* IDLE is to sit here and wait until something happens.
*/
if (iioex.bytesTransferred == 0)
r = null; // keep trying
else
// convert this into a BYE response
r = Response.byeResponse(iioex);
} catch (IOException ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
} catch (ProtocolException pex) {
// convert this into a BYE response
r = Response.byeResponse(pex);
}
}
return r;
}
/**
* Process a response returned by readIdleResponse().
* This method will be called with appropriate locks
* held so that the processing of the response is safe.
*
* @since JavaMail 1.4.1
*/
public boolean processIdleResponse(Response r) throws ProtocolException {
Response[] responses = new Response[1];
responses[0] = r;
boolean done = false; // done reading responses?
notifyResponseHandlers(responses);
if (r.isBYE()) // shouldn't wait for command completion response
done = true;
// If this is a matching command completion response, we are done
if (r.isTagged() && r.getTag().equals(idleTag))
done = true;
if (done)
idleTag = null; // no longer in IDLE
handleResult(r);
return !done;
}
// the DONE command to break out of IDLE
private static final byte[] DONE = { 'D', 'O', 'N', 'E', '\r', '\n' };
/**
* Abort an IDLE command. While one thread is blocked in
* readIdleResponse(), another thread will use this method
* to abort the IDLE command, which will cause the server
* to send the closing tag for the IDLE command, which
* readIdleResponse() and processIdleResponse() will see
* and terminate the IDLE state.
*
* @since JavaMail 1.4.1
*/
public void idleAbort() {
OutputStream os = getOutputStream();
try {
os.write(DONE);
os.flush();
} catch (Exception ex) {
// nothing to do, hope to detect it again later
logger.log(Level.FINEST, "Exception aborting IDLE", ex);
}
}
/**
* ID Command.
*
* @see "RFC 2971"
* @since JavaMail 1.5.1
*/
public Map id(Map clientParams)
throws ProtocolException {
if (!hasCapability("ID"))
throw new BadCommandException("ID not supported");
Response[] r = command("ID", ID.getArgumentList(clientParams));
ID id = null;
Response response = r[r.length-1];
// Grab ID response
if (response.isOK()) { // command succesful
for (int i = 0, len = r.length; i < len; i++) {
if (!(r[i] instanceof IMAPResponse))
continue;
IMAPResponse ir = (IMAPResponse)r[i];
if (ir.keyEquals("ID")) {
if (id == null)
id = new ID(ir);
r[i] = null;
}
}
}
// dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return id == null ? null : id.getServerParams();
}
}