org.eclipse.angus.mail.imap.protocol.IMAPProtocol Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2023 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 org.eclipse.angus.mail.imap.protocol;
import jakarta.mail.Flags;
import jakarta.mail.Folder;
import jakarta.mail.Quota;
import jakarta.mail.UIDFolder;
import jakarta.mail.internet.MimeUtility;
import jakarta.mail.search.SearchException;
import jakarta.mail.search.SearchTerm;
import org.eclipse.angus.mail.auth.Ntlm;
import org.eclipse.angus.mail.iap.Argument;
import org.eclipse.angus.mail.iap.BadCommandException;
import org.eclipse.angus.mail.iap.ByteArray;
import org.eclipse.angus.mail.iap.CommandFailedException;
import org.eclipse.angus.mail.iap.ConnectionException;
import org.eclipse.angus.mail.iap.Literal;
import org.eclipse.angus.mail.iap.LiteralException;
import org.eclipse.angus.mail.iap.ParsingException;
import org.eclipse.angus.mail.iap.Protocol;
import org.eclipse.angus.mail.iap.ProtocolException;
import org.eclipse.angus.mail.iap.Response;
import org.eclipse.angus.mail.imap.ACL;
import org.eclipse.angus.mail.imap.AppendUID;
import org.eclipse.angus.mail.imap.CopyUID;
import org.eclipse.angus.mail.imap.ResyncData;
import org.eclipse.angus.mail.imap.Rights;
import org.eclipse.angus.mail.imap.SortTerm;
import org.eclipse.angus.mail.imap.Utility;
import org.eclipse.angus.mail.util.ASCIIUtility;
import org.eclipse.angus.mail.util.BASE64EncoderStream;
import org.eclipse.angus.mail.util.MailLogger;
import org.eclipse.angus.mail.util.PropUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
/**
* 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 referralException; // throw exception for IMAP REFERRAL?
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.
private boolean utf8; // UTF-8 support enabled?
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.
*
* @throws ProtocolException for protocol failures
* @param name the protocol name
* @param host host to connect to
* @param port port number to connect to
* @param props Properties object used by this protocol
* @param isSSL true if SSL should be used
* @param logger the MailLogger to use for debug output
* @exception IOException for I/O errors
*/
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);
// in case it was not initialized in processGreeting
referralException = PropUtil.getBooleanProperty(props,
prefix + ".referralexception", 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 in the InputStream from which to read
* @param out the PrintStream to which to write
* @param props Properties object used by this protocol
* @param debug true to enable debugging output
* @exception IOException for I/O errors
*/
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.
*
* @return an array of FetchItem objects
* @since JavaMail 1.4.6
*/
public FetchItem[] getFetchItems() {
return fetchItems;
}
/**
* CAPABILITY command.
*
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.1.1"
*/
public void capability() throws ProtocolException {
// Check CAPABILITY
Response[] r = command("CAPABILITY", null);
Response response = r[r.length - 1];
if (response.isOK())
handleCapabilityResponse(r);
handleResult(response);
}
/**
* Handle any untagged CAPABILITY response in the Response array.
*
* @param r the responses
*/
public void handleCapabilityResponse(Response[] r) {
boolean first = true;
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")) {
if (first) {
// clear out current when first response seen
capabilities = new HashMap<>(10);
authmechs = new ArrayList<>(5);
first = false;
}
parseCapabilities(ir);
}
}
}
/**
* If the response contains a CAPABILITY response code, extract
* it and save the capabilities.
*
* @param r the response
*/
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.
*
* @param r the CAPABILITY 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.
*
* @param r the greeting response
* @exception ProtocolException for protocol failures
*/
@Override
protected void processGreeting(Response r) throws ProtocolException {
if (r.isBYE()) {
checkReferral(r); // may throw exception
throw new ConnectionException(this, r);
}
if (r.isOK()) { // check if it's OK
// XXX - is a REFERRAL response code really allowed here?
// XXX - referralException hasn't been initialized in c'tor yet
referralException = PropUtil.getBooleanProperty(props,
prefix + ".referralexception", false);
if (referralException)
checkReferral(r);
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 {
disconnect();
throw new ConnectionException(this, r);
}
}
/**
* Check for an IMAP login REFERRAL response code.
*
* @exception IMAPReferralException if REFERRAL response code found
* @see "RFC 2221"
*/
private void checkReferral(Response r) throws IMAPReferralException {
String s = r.getRest(); // get the text after the response
if (s.startsWith("[")) { // a response code
int i = s.indexOf(' ');
if (i > 0 && s.substring(1, i).equalsIgnoreCase("REFERRAL")) {
String url, msg;
int j = s.indexOf(']');
if (j > 0) { // should always be true;
url = s.substring(i + 1, j);
msg = s.substring(j + 1).trim();
} else {
url = s.substring(i + 1);
msg = "";
}
if (r.isBYE())
disconnect();
throw new IMAPReferralException(msg, url);
}
}
}
/**
* Returns true
if the connection has been authenticated,
* either due to a successful login, or due to a PREAUTH greeting response.
*
* @return true if the connection has been authenticated
*/
public boolean isAuthenticated() {
return authenticated;
}
/**
* Returns true
if this is an IMAP4rev1 server
*
* @return true if this is an IMAP4rev1 server
*/
public boolean isREV1() {
return rev1;
}
/**
* Returns whether this Protocol supports non-synchronizing literals.
*
* @return true if non-synchronizing literals are supported
*/
@Override
protected boolean supportsNonSyncLiterals() {
return hasCapability("LITERAL+");
}
/**
* Read a response from the server.
*
* @return the response
* @exception IOException for I/O errors
* @exception ProtocolException for protocol failures
*/
@Override
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.
*
* @param c the capability name
* @return true if the server has the capability
*/
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 (it.next().startsWith(c))
return true;
}
return false;
}
return capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
}
/**
* Return the map of capabilities returned by the server.
*
* @return the Map of capabilities
* @since JavaMail 1.4.1
*/
public Map getCapabilities() {
return capabilities;
}
/**
* Does the server support UTF-8?
*
* @since JavaMail 1.6.0
*/
public boolean supportsUtf8() {
return utf8;
}
/**
* Close socket connection.
*
* This method just makes the Protocol.disconnect() method
* public.
*/
@Override
public void disconnect() {
super.disconnect();
authenticated = false; // just in case
}
/**
* The NOOP command.
*
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.1.2"
*/
public void noop() throws ProtocolException {
logger.fine("IMAPProtocol noop");
simpleCommand("NOOP", null);
}
/**
* LOGOUT Command.
*
* @exception ProtocolException for protocol failures
* @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.
*
* @param u the username
* @param p the password
* @throws ProtocolException as thrown by {@link Protocol#handleResult}.
* @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();
}
// handle an illegal but not uncommon untagged CAPABILTY response
handleCapabilityResponse(r);
// dispatch untagged responses
notifyResponseHandlers(r);
// Handle result of this command
if (noauthdebug && isTracing())
logger.fine("LOGIN command result: " + r[r.length - 1]);
handleLoginResult(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
*
* @param u the username
* @param p the password
* @throws ProtocolException as thrown by {@link Protocol#handleResult}.
* @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(s.getBytes(StandardCharsets.UTF_8));
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;
// hmm .. unsolicited response here ?!
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
v.add(r);
}
} finally {
resumeTracing();
}
Response[] responses = v.toArray(new Response[0]);
// handle an illegal but not uncommon untagged CAPABILTY response
handleCapabilityResponse(responses);
/*
* 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.
*/
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
if (noauthdebug && isTracing())
logger.fine("AUTHENTICATE LOGIN command result: " + r);
handleLoginResult(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(s.getBytes(StandardCharsets.UTF_8));
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;
// hmm .. unsolicited response here ?!
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
v.add(r);
}
} finally {
resumeTracing();
}
Response[] responses = v.toArray(new Response[0]);
// handle an illegal but not uncommon untagged CAPABILTY response
handleCapabilityResponse(responses);
/*
* 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.
*/
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
if (noauthdebug && isTracing())
logger.fine("AUTHENTICATE PLAIN command result: " + r);
handleLoginResult(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;
int flags = PropUtil.getIntProperty(props,
"mail." + name + ".auth.ntlm.flags", 0);
boolean v2 = PropUtil.getBooleanProperty(props,
"mail." + name + ".auth.ntlm.v2", true);
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, v2);
first = false;
} else {
s = ntlm.generateType3Msg(r.getRest());
}
os.write(s.getBytes(StandardCharsets.UTF_8));
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;
// hmm .. unsolicited response here ?!
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
v.add(r);
}
} finally {
resumeTracing();
}
Response[] responses = v.toArray(new Response[0]);
// handle an illegal but not uncommon untagged CAPABILTY response
handleCapabilityResponse(responses);
/*
* 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.
*/
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
if (noauthdebug && isTracing())
logger.fine("AUTHENTICATE NTLM command result: " + r);
handleLoginResult(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=XOAUTH2 authentication scheme.
* This is based heavly on the {@link #authlogin} method.
*
* @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.5.5
*/
public synchronized void authoauth2(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 XOAUTH2 command trace suppressed");
suspendTracing();
}
try {
Argument args = new Argument();
args.writeAtom("XOAUTH2");
if (hasCapability("SASL-IR")) {
String resp = "user=" + u + "\001auth=Bearer " + p + "\001\001";
byte[] ba = Base64.getEncoder().encode(
resp.getBytes(StandardCharsets.UTF_8));
String irs = ASCIIUtility.toString(ba, 0, ba.length);
args.writeAtom(irs);
}
tag = writeCommand("AUTHENTICATE", args);
} catch (Exception ex) {
// Convert this into a BYE response
r = Response.byeResponse(ex);
done = true;
}
OutputStream os = getOutputStream(); // stream to IMAP server
while (!done) { // loop till we are done
try {
r = readResponse();
if (r.isContinuation()) {
// Server challenge ..
String resp = "user=" + u + "\001auth=Bearer " +
p + "\001\001";
byte[] b = Base64.getEncoder().encode(
resp.getBytes(StandardCharsets.UTF_8));
os.write(b); // write out response
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;
// hmm .. unsolicited response here ?!
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
v.add(r);
}
} finally {
resumeTracing();
}
Response[] responses = v.toArray(new Response[0]);
// handle an illegal but not uncommon untagged CAPABILTY response
handleCapabilityResponse(responses);
/*
* 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.
*/
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
if (noauthdebug && isTracing())
logger.fine("AUTHENTICATE XOAUTH2 command result: " + r);
handleLoginResult(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.
*
* @param allowed the SASL mechanisms we're allowed to use
* @param realm the SASL realm
* @param authzid the authorization id
* @param u the username
* @param p the password
* @exception ProtocolException for protocol failures
*/
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(
"org.eclipse.angus.mail.imap.protocol.IMAPSaslAuthenticator");
Constructor> c = sac.getConstructor(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 = v.toArray(new String[0]);
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();
}
/**
* Handle the result response for a LOGIN or AUTHENTICATE command.
* Look for IMAP login REFERRAL.
*
* @param r the response
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.5
*/
protected void handleLoginResult(Response r) throws ProtocolException {
if (hasCapability("LOGIN-REFERRALS") &&
(!r.isOK() || referralException))
checkReferral(r);
handleResult(r);
}
/**
* PROXYAUTH Command.
*
* @param u the PROXYAUTH user name
* @exception ProtocolException for protocol failures
* @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.
*
* @return the PROXYAUTH user name
* @since JavaMail 1.5.1
*/
public String getProxyAuthUser() {
return proxyAuthUser;
}
/**
* UNAUTHENTICATE Command.
*
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.1
* @see "Netscape/iPlanet/SunONE Messaging Server extension"
*/
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.
*
* @param guid the GUID
* @exception ProtocolException for protocol failures
* @since JavaMail 1.4.4
* @deprecated As of JavaMail 1.5.1, replaced by
* {@link #id(Map) id(Map<String,String>)}
*/
@Deprecated
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.
*
* @exception ProtocolException for protocol failures
* @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);
}
}
/**
* COMPRESS Command. Only supports DEFLATE.
*
* @exception ProtocolException for protocol failures
* @see "RFC 4978"
*/
public void compress() throws ProtocolException {
try {
super.startCompression("COMPRESS DEFLATE");
} catch (ProtocolException pex) {
logger.log(Level.FINE, "COMPRESS 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, "COMPRESS 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("COMPRESS failure", ex);
}
}
/**
* Encode a mailbox name appropriately depending on whether or not
* the server supports UTF-8, and add the encoded name to the
* Argument.
*
* @param args the arguments
* @param name the name to encode
* @since JavaMail 1.6.0
*/
protected void writeMailboxName(Argument args, String name) {
if (utf8)
args.writeString(name, StandardCharsets.UTF_8);
else
// encode the mbox as per RFC2060
args.writeString(BASE64MailboxEncoder.encode(name));
}
/**
* SELECT Command.
*
* @param mbox the mailbox name
* @return MailboxInfo if successful
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.3.1"
*/
public MailboxInfo select(String mbox) throws ProtocolException {
return select(mbox, null);
}
/**
* SELECT Command with QRESYNC data.
*
* @param mbox the mailbox name
* @param rd the ResyncData
* @return MailboxInfo if successful
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.1
* @see "RFC2060, section 6.3.1"
* @see "RFC5162, section 3.1"
*/
public MailboxInfo select(String mbox, ResyncData rd)
throws ProtocolException {
Argument args = new Argument();
writeMailboxName(args, 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().contains("READ-ONLY"))
minfo.mode = Folder.READ_ONLY;
else
minfo.mode = Folder.READ_WRITE;
}
handleResult(response);
return minfo;
}
/**
* EXAMINE Command.
*
* @param mbox the mailbox name
* @return MailboxInfo if successful
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.3.2"
*/
public MailboxInfo examine(String mbox) throws ProtocolException {
return examine(mbox, null);
}
/**
* EXAMINE Command with QRESYNC data.
*
* @param mbox the mailbox name
* @param rd the ResyncData
* @return MailboxInfo if successful
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.1
* @see "RFC2060, section 6.3.2"
* @see "RFC5162, section 3.1"
*/
public MailboxInfo examine(String mbox, ResyncData rd)
throws ProtocolException {
Argument args = new Argument();
writeMailboxName(args, 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.
*
* @param cap the name of the capability to enable
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.1
* @see "RFC 5161"
*/
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));
// update the utf8 flag
utf8 = isEnabled("UTF8=ACCEPT");
}
/**
* Is the capability/extension enabled?
*
* @param cap the capability name
* @return true if enabled
* @since JavaMail 1.5.1
* @see "RFC 5161"
*/
public boolean isEnabled(String cap) {
if (enabled == null)
return false;
else
return enabled.contains(cap.toUpperCase(Locale.ENGLISH));
}
/**
* UNSELECT Command.
*
* @exception ProtocolException for protocol failures
* @since JavaMail 1.4.4
* @see "RFC 3691"
*/
public void unselect() throws ProtocolException {
if (!hasCapability("UNSELECT"))
throw new BadCommandException("UNSELECT not supported");
simpleCommand("UNSELECT", null);
}
/**
* STATUS Command.
*
* @param mbox the mailbox
* @param items the STATUS items to request
* @return STATUS results
* @exception ProtocolException for protocol failures
* @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");
Argument args = new Argument();
writeMailboxName(args, 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.
*
* @param mbox the mailbox to create
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.3.3"
*/
public void create(String mbox) throws ProtocolException {
Argument args = new Argument();
writeMailboxName(args, mbox);
simpleCommand("CREATE", args);
}
/**
* DELETE Command.
*
* @param mbox the mailbox to delete
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.3.4"
*/
public void delete(String mbox) throws ProtocolException {
Argument args = new Argument();
writeMailboxName(args, mbox);
simpleCommand("DELETE", args);
}
/**
* RENAME Command.
*
* @param o old mailbox name
* @param n new mailbox name
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.3.5"
*/
public void rename(String o, String n) throws ProtocolException {
Argument args = new Argument();
writeMailboxName(args, o);
writeMailboxName(args, n);
simpleCommand("RENAME", args);
}
/**
* SUBSCRIBE Command.
*
* @param mbox the mailbox
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.3.6"
*/
public void subscribe(String mbox) throws ProtocolException {
Argument args = new Argument();
writeMailboxName(args, mbox);
simpleCommand("SUBSCRIBE", args);
}
/**
* UNSUBSCRIBE Command.
*
* @param mbox the mailbox
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.3.7"
*/
public void unsubscribe(String mbox) throws ProtocolException {
Argument args = new Argument();
writeMailboxName(args, mbox);
simpleCommand("UNSUBSCRIBE", args);
}
/**
* LIST Command.
*
* @param ref reference string
* @param pattern pattern to list
* @return LIST results
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.3.8"
*/
public ListInfo[] list(String ref, String pattern)
throws ProtocolException {
return doList("LIST", ref, pattern);
}
/**
* LSUB Command.
*
* @param ref reference string
* @param pattern pattern to list
* @return LSUB results
* @exception ProtocolException for protocol failures
* @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.
*
* @param cmd the list command
* @param ref the reference string
* @param pat the pattern
* @return array of ListInfo results
* @exception ProtocolException for protocol failures
* @since JavaMail 1.4.6
*/
protected ListInfo[] doList(String cmd, String ref, String pat)
throws ProtocolException {
Argument args = new Argument();
writeMailboxName(args, ref);
writeMailboxName(args, 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[0]);
}
}
// Dispatch remaining untagged responses
notifyResponseHandlers(r);
handleResult(response);
return linfo;
}
/**
* APPEND Command.
*
* @param mbox the mailbox
* @param f the message Flags
* @param d the message date
* @param data the message data
* @exception ProtocolException for protocol failures
* @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.
*
* @param mbox the mailbox
* @param f the message Flags
* @param d the message date
* @param data the message data
* @return APPENDUID data
* @exception ProtocolException for protocol failures
* @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 {
Argument args = new Argument();
writeMailboxName(args, 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.
*
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.4.1"
*/
public void check() throws ProtocolException {
simpleCommand("CHECK", null);
}
/**
* CLOSE Command.
*
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.4.2"
*/
public void close() throws ProtocolException {
simpleCommand("CLOSE", null);
}
/**
* EXPUNGE Command.
*
* @exception ProtocolException for protocol failures
* @see "RFC2060, section 6.4.3"
*/
public void expunge() throws ProtocolException {
simpleCommand("EXPUNGE", null);
}
/**
* UID EXPUNGE Command.
*
* @param set UIDs to expunge
* @exception ProtocolException for protocol failures
* @see "RFC4315, section 2"
*/
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.
*
* @param msgno the message number
* @return the BODYSTRUCTURE item
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgno the message number
* @param section the body section
* @return the BODY item
* @exception ProtocolException for protocol failures
*/
public BODY peekBody(int msgno, String section)
throws ProtocolException {
return fetchBody(msgno, section, true);
}
/**
* Fetch given BODY section.
*
* @param msgno the message number
* @param section the body section
* @return the BODY item
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgno the message number
* @param section the body section
* @param start starting byte count
* @param size number of bytes to fetch
* @return the BODY item
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgno the message number
* @param section the body section
* @param start starting byte count
* @param size number of bytes to fetch
* @return the BODY item
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgno the message number
* @param section the body section
* @param start starting byte count
* @param size number of bytes to fetch
* @param ba the buffer into which to read the response
* @return the BODY item
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgno the message number
* @param section the body section
* @param start starting byte count
* @param size number of bytes to fetch
* @param ba the buffer into which to read the response
* @return the BODY item
* @exception ProtocolException for protocol failures
*/
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".
*
* @param msgno the message number
* @param section the body section
* @param body the body string
* @return the BODY item
* @exception ProtocolException for protocol failures
*/
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.
*
* @return the buffer to use
*/
@Override
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.
*
* @param msgno the message number
* @param what the item to fetch
* @return the RFC822DATA item
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgno the message number
* @return the Flags
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgno the message number
* @return the UID
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgno the message number
* @return the MODSEQ
* @exception ProtocolException for protocol failures
* @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. Nothing is returned;
* the FETCH UID response must be handled by the reponse handler,
* along with any possible EXPUNGE responses, to ensure that the
* UID is matched with the correct sequence number.
*
* @param uid the UID
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.3
*/
public void fetchSequenceNumber(long uid) throws ProtocolException {
Response[] r = fetch(String.valueOf(uid), "UID", true);
notifyResponseHandlers(r);
handleResult(r[r.length - 1]);
}
/**
* Get the sequence numbers for UIDs ranging from start till end.
* Since the range may be large and sparse, an array of the UIDs actually
* found is returned. The caller must map these to messages after
* the FETCH UID responses have been handled by the reponse handler,
* along with any possible EXPUNGE responses, to ensure that the
* UIDs are matched with the correct sequence numbers.
*
* @param start first UID
* @param end last UID
* @return array of sequence numbers
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.3
*/
public long[] 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]);
long[] lv = new long[v.size()];
for (int i = 0; i < v.size(); i++)
lv[i] = v.get(i).uid;
return lv;
}
/**
* Get the sequence numbers for UIDs specified in the array.
* Nothing is returned. The caller must map the UIDs to messages after
* the FETCH UID responses have been handled by the reponse handler,
* along with any possible EXPUNGE responses, to ensure that the
* UIDs are matched with the correct sequence numbers.
*
* @param uids the UIDs
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.3
*/
public void fetchSequenceNumbers(long[] uids) throws ProtocolException {
StringBuilder sb = new StringBuilder();
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);
notifyResponseHandlers(r);
handleResult(r[r.length - 1]);
}
/**
* 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.
*
* @param start first UID
* @param end last UID
* @param modseq the MODSEQ
* @return array of sequence numbers
* @exception ProtocolException for protocol failures
* @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] = 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.
*
* @param msgsets the messages to copy
* @param mbox the mailbox to copy them to
* @exception ProtocolException for protocol failures
*/
public void copy(MessageSet[] msgsets, String mbox)
throws ProtocolException {
copyuid(MessageSet.toString(msgsets), mbox, false);
}
/**
* COPY command.
*
* @param start start message number
* @param end end message number
* @param mbox the mailbox to copy them to
* @exception ProtocolException for protocol failures
*/
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.
*
* @param msgsets the messages to copy
* @param mbox the mailbox to copy them to
* @return COPYUID response data
* @exception ProtocolException for protocol failures
* @see "RFC 4315, section 3"
*/
public CopyUID copyuid(MessageSet[] msgsets, String mbox)
throws ProtocolException {
return copyuid(MessageSet.toString(msgsets), mbox, true);
}
/**
* COPY command, return uid from COPYUID response code.
*
* @param start start message number
* @param end end message number
* @param mbox the mailbox to copy them to
* @return COPYUID response data
* @exception ProtocolException for protocol failures
* @see "RFC 4315, section 3"
*/
public CopyUID copyuid(int start, int end, String mbox)
throws ProtocolException {
return copyuid(String.valueOf(start) + ":" + String.valueOf(end),
mbox, true);
}
private CopyUID copyuid(String msgSequence, String mbox, boolean uid)
throws ProtocolException {
if (uid && !hasCapability("UIDPLUS"))
throw new BadCommandException("UIDPLUS not supported");
Argument args = new Argument();
args.writeAtom(msgSequence);
writeMailboxName(args, 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);
else
return null;
}
/**
* MOVE command.
*
* @param msgsets the messages to move
* @param mbox the mailbox to move them to
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.4
* @see "RFC 6851"
*/
public void move(MessageSet[] msgsets, String mbox)
throws ProtocolException {
moveuid(MessageSet.toString(msgsets), mbox, false);
}
/**
* MOVE command.
*
* @param start start message number
* @param end end message number
* @param mbox the mailbox to move them to
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.4
* @see "RFC 6851"
*/
public void move(int start, int end, String mbox)
throws ProtocolException {
moveuid(String.valueOf(start) + ":" + String.valueOf(end),
mbox, false);
}
/**
* MOVE Command, return uid from COPYUID response code.
*
* @param msgsets the messages to move
* @param mbox the mailbox to move them to
* @return COPYUID response data
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.4
* @see "RFC 6851"
* @see "RFC 4315, section 3"
*/
public CopyUID moveuid(MessageSet[] msgsets, String mbox)
throws ProtocolException {
return moveuid(MessageSet.toString(msgsets), mbox, true);
}
/**
* MOVE Command, return uid from COPYUID response code.
*
* @param start start message number
* @param end end message number
* @param mbox the mailbox to move them to
* @return COPYUID response data
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.4
* @see "RFC 6851"
* @see "RFC 4315, section 3"
*/
public CopyUID moveuid(int start, int end, String mbox)
throws ProtocolException {
return moveuid(String.valueOf(start) + ":" + String.valueOf(end),
mbox, true);
}
/**
* MOVE Command, return uid from COPYUID response code.
*
* @since JavaMail 1.5.4
* @see "RFC 6851"
* @see "RFC 4315, section 3"
*/
private CopyUID moveuid(String msgSequence, String mbox, boolean uid)
throws ProtocolException {
if (!hasCapability("MOVE"))
throw new BadCommandException("MOVE not supported");
if (uid && !hasCapability("UIDPLUS"))
throw new BadCommandException("UIDPLUS not supported");
Argument args = new Argument();
args.writeAtom(msgSequence);
writeMailboxName(args, mbox);
Response[] r = command("MOVE", args);
// dispatch untagged responses
notifyResponseHandlers(r);
// Handle result of this command
handleResult(r[r.length - 1]);
if (uid)
return getCopyUID(r);
else
return null;
}
/**
* If the response contains a COPYUID response code, extract
* it and return a CopyUID object with the information.
*
* @param rr the responses to examine
* @return the COPYUID response code data, or null if not found
* @since JavaMail 1.5.4
*/
protected CopyUID getCopyUID(Response[] rr) {
// most likely in the last response, so start there and work backward
for (int i = rr.length - 1; i >= 0; i--) {
Response r = rr[i];
if (r == null || !r.isOK())
continue;
byte b;
while ((b = r.readByte()) > 0 && b != (byte) '[')
;
if (b == 0)
continue;
String s;
s = r.readAtom();
if (!s.equalsIgnoreCase("COPYUID"))
continue;
// XXX - need to merge more than one response for MOVE?
long uidvalidity = r.readLong();
String src = r.readAtom();
String dst = r.readAtom();
return new CopyUID(uidvalidity,
UIDSet.parseUIDSets(src), UIDSet.parseUIDSets(dst));
}
return null;
}
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.
*
* @param msg the message number
* @param flags the flags
* @param set true to set, false to clear
* @exception ProtocolException for protocol failures
*/
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.
*
* @param flags the flags
* @return the IMAP flag_list
* @since JavaMail 1.5.4
*/
protected String createFlagList(Flags flags) {
StringBuilder sb = new StringBuilder("("); // 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.
* @exception ProtocolException for protocol failures
* @exception SearchException for search failures
*/
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.
* @exception ProtocolException for protocol failures
* @exception SearchException for search failures
*/
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,
// or if utf8 support has been enabled (in which case CHARSET
// is not allowed; see RFC 6855, section 3, last paragraph)
if (supportsUtf8() || SearchSequence.isAscii(term)) {
try {
return issueSearch(msgSequence, term, null);
} catch (IOException ioex) { /* will not happen */ }
}
/*
* The search "text" terms do contain non-ASCII chars and utf8
* support has not been enabled. 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 | SearchException pex) {
throw pex;
}
}
// 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 list 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.
*
* @return the SearchSequence
* @since JavaMail 1.4.6
*/
protected SearchSequence getSearchSequence() {
if (searchSequence == null)
searchSequence = new SearchSequence(this);
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.
* @exception ProtocolException for protocol failures
* @exception SearchException for search failures
* @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 list 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.
*
* @return the namespaces
* @exception ProtocolException for protocol failures
* @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.
*
* @param mbox the mailbox
* @return array of Quota objects
* @exception ProtocolException for protocol failures
* @see "RFC2087"
*/
public Quota[] getQuotaRoot(String mbox) throws ProtocolException {
if (!hasCapability("QUOTA"))
throw new BadCommandException("GETQUOTAROOT not supported");
Argument args = new Argument();
writeMailboxName(args, mbox);
Response[] r = command("GETQUOTAROOT", args);
Response response = r[r.length - 1];
Map tab = new HashMap<>();
// 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 = 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);
return tab.values().toArray(new Quota[0]);
}
/**
* GETQUOTA Command.
*
* Returns an array of Quota objects, representing the quotas
* for this quotaroot.
*
* @param root the quotaroot
* @return the quotas
* @exception ProtocolException for protocol failures
* @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); // XXX - could be UTF-8?
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[0]);
}
/**
* SETQUOTA Command.
*
* Set the indicated quota on the corresponding quotaroot.
*
* @param quota the quota to set
* @exception ProtocolException for protocol failures
* @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); // XXX - could be UTF-8?
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.isNextNonSpace(')')) {
// 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);
}
}
q.resources = v.toArray(new Quota.Resource[0]);
return q;
}
/**
* SETACL Command.
*
* @param mbox the mailbox
* @param modifier the ACL modifier
* @param acl the ACL
* @exception ProtocolException for protocol failures
* @see "RFC2086"
*/
public void setACL(String mbox, char modifier, ACL acl)
throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
Argument args = new Argument();
writeMailboxName(args, 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.
*
* @param mbox the mailbox
* @param user the user
* @exception ProtocolException for protocol failures
* @see "RFC2086"
*/
public void deleteACL(String mbox, String user) throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
Argument args = new Argument();
writeMailboxName(args, mbox);
args.writeString(user); // XXX - could be UTF-8?
Response[] r = command("DELETEACL", args);
Response response = r[r.length - 1];
// dispatch untagged responses
notifyResponseHandlers(r);
handleResult(response);
}
/**
* GETACL Command.
*
* @param mbox the mailbox
* @return the ACL array
* @exception ProtocolException for protocol failures
* @see "RFC2086"
*/
public ACL[] getACL(String mbox) throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
Argument args = new Argument();
writeMailboxName(args, 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[0]);
}
/**
* LISTRIGHTS Command.
*
* @param mbox the mailbox
* @param user the user rights to return
* @return the rights array
* @exception ProtocolException for protocol failures
* @see "RFC2086"
*/
public Rights[] listRights(String mbox, String user)
throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
Argument args = new Argument();
writeMailboxName(args, mbox);
args.writeString(user); // XXX - could be UTF-8?
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[0]);
}
/**
* MYRIGHTS Command.
*
* @param mbox the mailbox
* @return the rights
* @exception ProtocolException for protocol failures
* @see "RFC2086"
*/
public Rights myRights(String mbox) throws ProtocolException {
if (!hasCapability("ACL"))
throw new BadCommandException("ACL not supported");
Argument args = new Argument();
writeMailboxName(args, 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.
*
* @exception ProtocolException for protocol failures
* @since JavaMail 1.4.1
* @see "RFC2177"
*/
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[0]);
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.
*
* @return the response
* @since JavaMail 1.4.1
*/
public synchronized Response readIdleResponse() {
if (idleTag == null)
return null; // IDLE not in progress
Response r = null;
try {
r = readResponse();
} catch (IOException | ProtocolException ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
}
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.
*
* @param r the response
* @return true if IDLE is done
* @exception ProtocolException for protocol failures
* @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.
*
* @param clientParams map of names and values
* @return map of names and values from server
* @exception ProtocolException for protocol failures
* @since JavaMail 1.5.1
* @see "RFC 2971"
*/
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();
}
}