📄 imapprotocol.java
字号:
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. 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.html
* or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [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.
*/
/*
* @(#)IMAPProtocol.java 1.64 07/05/04
*/
package com.sun.mail.imap.protocol;
import java.io.*;
import java.util.*;
import java.text.*;
import java.lang.reflect.*;
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.imap.ACL;
import com.sun.mail.imap.Rights;
import com.sun.mail.imap.AppendUID;
/**
* 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 <code>notifyResponseHandlers(r)</code>.
*
* @version 1.64, 07/05/04
* @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 authenticated; // authenticated?
// WARNING: authenticated may be set to true in superclass
// constructor, don't initialize it here.
private Map capabilities = null;
private List authmechs = null;
private String[] searchCharsets; // array of search charsets
private String name;
private SaslAuthenticator saslAuthenticator; // if SASL is being used
private ByteArray ba; // a buffer for fetchBody
private static final byte[] CRLF = { (byte)'\r', (byte)'\n'};
/**
* 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,
boolean debug, PrintStream out, Properties props,
boolean isSSL) throws IOException, ProtocolException {
super(host, port, debug, out, props, "mail." + name, isSSL);
try {
this.name = name;
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();
}
}
/**
* 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 (debug)
out.println("IMAP DEBUG: 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
IMAPResponse ir = (IMAPResponse)r;
if (ir.keyEquals("PREAUTH")) {
authenticated = true;
setCapabilities(r);
} else
throw new ConnectionException(this, r);
}
/**
* Returns <code>true</code> 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 <code>true</code> 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
return IMAPResponse.readResponse(this);
}
/**
* Check whether the given capability is supported by
* this server. Returns <code>true</code> if so, otherwise
* returns false.
*/
public boolean hasCapability(String c) {
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 {
if (debug)
out.println("IMAP DEBUG: IMAPProtocol noop");
simpleCommand("NOOP", null);
}
/**
* LOGOUT Command.
*
* @see "RFC2060, section 6.1.3"
*/
public void logout() throws ProtocolException {
// XXX - what happens if exception is thrown?
Response[] r = command("LOGOUT", null);
authenticated = false;
// dispatch any unsolicited responses.
// NOTE that the BYE response is dispatched here as well
notifyResponseHandlers(r);
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 = command("LOGIN", args);
// dispatch untagged responses
notifyResponseHandlers(r);
// Handle result of this command
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 {
Vector v = new Vector();
String tag = null;
Response r = null;
boolean done = false;
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.addElement(r);
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
}
/* 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 = new Response[v.size()];
v.copyInto(responses);
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
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 {
Vector v = new Vector();
String tag = null;
Response r = null;
boolean done = false;
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 + 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.addElement(r);
} catch (Exception ioex) {
// convert this into a BYE response
r = Response.byeResponse(ioex);
done = true;
}
}
/* 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 = new Response[v.size()];
v.copyInto(responses);
notifyResponseHandlers(responses);
// Handle the final OK, NO, BAD or BYE response
handleResult(r);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -