📄 smtptransport.java
字号:
if (resp == 530) {
startTLS();
resp = simpleCommand("AUTH LOGIN");
}
/*
* Wrap a BASE64Encoder around a ByteArrayOutputstream
* to craft b64 encoded username and password 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!
*/
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
OutputStream b64os =
new BASE64EncoderStream(bos, Integer.MAX_VALUE);
if (resp == 334) {
// obtain b64 encoded bytes
b64os.write(ASCIIUtility.getBytes(user));
b64os.flush(); // complete the encoding
// send username
resp = simpleCommand(bos.toByteArray());
bos.reset(); // reset buffer
}
if (resp == 334) {
// obtain b64 encoded bytes
b64os.write(ASCIIUtility.getBytes(passwd));
b64os.flush(); // complete the encoding
// send passwd
resp = simpleCommand(bos.toByteArray());
bos.reset(); // reset buffer
}
} catch (IOException ex) { // should never happen, ignore
} finally {
if (resp != 235) {
closeConnection();
return false;
}
}
} else if (supportsAuthentication("PLAIN")) {
// XXX - could use "initial response" capability
int resp = simpleCommand("AUTH PLAIN");
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
OutputStream b64os =
new BASE64EncoderStream(bos, Integer.MAX_VALUE);
if (resp == 334) {
// send "<NUL>user<NUL>passwd"
// XXX - we don't send an authorization identity
b64os.write(0);
b64os.write(ASCIIUtility.getBytes(user));
b64os.write(0);
b64os.write(ASCIIUtility.getBytes(passwd));
b64os.flush(); // complete the encoding
// send username
resp = simpleCommand(bos.toByteArray());
}
} catch (IOException ex) { // should never happen, ignore
} finally {
if (resp != 235) {
closeConnection();
return false;
}
}
} else if (supportsAuthentication("DIGEST-MD5") &&
(md5 = getMD5()) != null) {
int resp = simpleCommand("AUTH DIGEST-MD5");
try {
if (resp == 334) {
byte[] b = md5.authClient(host, user, passwd,
getSASLRealm(), lastServerResponse);
resp = simpleCommand(b);
if (resp == 334) { // client authenticated by server
if (!md5.authServer(lastServerResponse)) {
// server NOT authenticated by client !!!
resp = -1;
} else {
// send null response
resp = simpleCommand(new byte[0]);
}
}
}
} catch (Exception ex) { // should never happen, ignore
if (debug)
out.println("DEBUG SMTP: DIGEST-MD5: " + ex);
} finally {
if (resp != 235) {
closeConnection();
return false;
}
}
}
}
// we connected correctly
return true;
}
/**
* Send the Message to the specified list of addresses.<p>
*
* If all the <code>addresses</code> succeed the SMTP check
* using the <code>RCPT TO:</code> command, we attempt to send the message.
* A TransportEvent of type MESSAGE_DELIVERED is fired indicating the
* successful submission of a message to the SMTP host.<p>
*
* If some of the <code>addresses</code> fail the SMTP check,
* and the <code>mail.stmp.sendpartial</code> property is not set,
* sending is aborted. The TransportEvent of type MESSAGE_NOT_DELIVERED
* is fired containing the valid and invalid addresses. The
* SendFailedException is also thrown. <p>
*
* If some of the <code>addresses</code> fail the SMTP check,
* and the <code>mail.stmp.sendpartial</code> property is set to true,
* the message is sent. The TransportEvent of type
* MESSAGE_PARTIALLY_DELIVERED
* is fired containing the valid and invalid addresses. The
* SMTPSendFailedException is also thrown. <p>
*
* MessagingException is thrown if the message can't write out
* an RFC822-compliant stream using its <code>writeTo</code> method. <p>
*
* @param message The MimeMessage to be sent
* @param addresses List of addresses to send this message to
* @see javax.mail.event.TransportEvent
* @exception SMTPSendFailedException if the send failed because of
* an SMTP command error
* @exception SendFailedException if the send failed because of
* invalid addresses.
* @exception MessagingException if the connection is dead
* or not in the connected state or if the message is
* not a MimeMessage.
*/
public synchronized void sendMessage(Message message, Address[] addresses)
throws MessagingException, SendFailedException {
checkConnected();
// check if the message is a valid MIME/RFC822 message and that
// it has all valid InternetAddresses; fail if not
if (!(message instanceof MimeMessage)) {
if (debug)
out.println("DEBUG SMTP: Can only send RFC822 msgs");
throw new MessagingException("SMTP can only send RFC822 messages");
}
for (int i = 0; i < addresses.length; i++) {
if (!(addresses[i] instanceof InternetAddress)) {
throw new MessagingException(addresses[i] +
" is not an InternetAddress");
}
}
this.message = (MimeMessage)message;
this.addresses = addresses;
validUnsentAddr = addresses; // until we know better
expandGroups();
boolean use8bit = false;
if (message instanceof SMTPMessage)
use8bit = ((SMTPMessage)message).getAllow8bitMIME();
if (!use8bit) {
String ebStr =
session.getProperty("mail." + name + ".allow8bitmime");
use8bit = ebStr != null && ebStr.equalsIgnoreCase("true");
}
if (debug)
out.println("DEBUG SMTP: use8bit " + use8bit);
if (use8bit && supportsExtension("8BITMIME")) {
if (convertTo8Bit(this.message)) {
// in case we made any changes, save those changes
// XXX - this will change the Message-ID
try {
this.message.saveChanges();
} catch (MessagingException mex) {
// ignore it
}
}
}
try {
mailFrom();
rcptTo();
this.message.writeTo(data(), ignoreList);
finishData();
if (sendPartiallyFailed) {
// throw the exception,
// fire TransportEvent.MESSAGE_PARTIALLY_DELIVERED event
if (debug)
out.println("DEBUG SMTP: Sending partially failed " +
"because of invalid destination addresses");
notifyTransportListeners(
TransportEvent.MESSAGE_PARTIALLY_DELIVERED,
validSentAddr, validUnsentAddr, invalidAddr,
this.message);
throw new SMTPSendFailedException(".", lastReturnCode,
lastServerResponse, exception,
validSentAddr, validUnsentAddr, invalidAddr);
}
notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
validSentAddr, validUnsentAddr,
invalidAddr, this.message);
} catch (MessagingException mex) {
if (debug)
mex.printStackTrace(out);
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
validSentAddr, validUnsentAddr,
invalidAddr, this.message);
throw mex;
} catch (IOException ex) {
if (debug)
ex.printStackTrace(out);
// if we catch an IOException, it means that we want
// to drop the connection so that the message isn't sent
try {
closeConnection();
} catch (MessagingException mex) { /* ignore it */ }
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
validSentAddr, validUnsentAddr,
invalidAddr, this.message);
throw new MessagingException("IOException while sending message",
ex);
} finally {
// no reason to keep this data around
validSentAddr = validUnsentAddr = invalidAddr = null;
this.addresses = null;
this.message = null;
this.exception = null;
sendPartiallyFailed = false;
}
}
/**
* Close the Transport and terminate the connection to the server.
*/
public synchronized void close() throws MessagingException {
if (!super.isConnected()) // Already closed.
return;
try {
if (serverSocket != null) {
sendCommand("QUIT");
if (quitWait) {
int resp = readServerResponse();
if (resp != 221 && resp != -1)
out.println("DEBUG SMTP: QUIT failed with " + resp);
}
}
} finally {
closeConnection();
}
}
private void closeConnection() throws MessagingException {
try {
if (serverSocket != null)
serverSocket.close();
} catch (IOException ioex) { // shouldn't happen
throw new MessagingException("Server Close Failed", ioex);
} finally {
serverSocket = null;
serverOutput = null;
serverInput = null;
lineInputStream = null;
if (super.isConnected()) // only notify if already connected
super.close();
}
}
/**
* Check whether the transport is connected. Override superclass
* method, to actually ping our server connection.
*/
public synchronized boolean isConnected() {
if (!super.isConnected())
// if we haven't been connected at all, don't bother with NOOP
return false;
try {
// sendmail may respond slowly to NOOP after many requests
// so if mail.smtp.userset is set we use RSET instead of NOOP.
if (useRset)
sendCommand("RSET");
else
sendCommand("NOOP");
int resp = readServerResponse();
// NOOP should return 250 on success, however, SIMS 3.2 returns
// 200, so we work around it.
//
// Hotmail doesn't implement the NOOP command at all so assume
// any kind of response means we're still connected.
// That is, any response except 421, which means the server
// is shutting down the connection.
//
if (resp >= 0 && resp != 421) {
return true;
} else {
try {
closeConnection();
} catch (MessagingException mex) { } // ignore it
return false;
}
} catch (Exception ex) {
try {
closeConnection();
} catch (MessagingException mex) { } // ignore it
return false;
}
}
/**
* Expand any group addresses.
*/
private void expandGroups() {
Vector groups = null;
for (int i = 0; i < addresses.length; i++) {
InternetAddress a = (InternetAddress)addresses[i];
if (a.isGroup()) {
if (groups == null) {
// first group, catch up with where we are
groups = new Vector();
for (int k = 0; k < i; k++)
groups.addElement(addresses[k]);
}
// parse it and add each individual address
try {
InternetAddress[] ia = a.getGroup(true);
if (ia != null) {
for (int j = 0; j < ia.length; j++)
groups.addElement(ia[j]);
} else
groups.addElement(a);
} catch (ParseException pex) {
// parse failed, add the whole thing
groups.addElement(a);
}
} else {
// if we've started accumulating a list, add this to it
if (groups != null)
groups.addElement(a);
}
}
// if we have a new list, convert it back to an array
if (groups != null) {
InternetAddress[] newa = new InternetAddress[groups.size()];
groups.copyInto(newa);
addresses = newa;
}
}
/**
* If the Part is a text part and has a Content-Transfer-Encoding
* of "quoted-printable" or "base64", and it obeys the rules for
* "8bit" encoding, change the encoding to "8bit". If the part is
* a multipart, recursively process all its parts.
*
* @return true if any changes were made
*
* XXX - This is really quite a hack.
*/
private boolean convertTo8Bit(MimePart part) {
boolean changed = false;
try {
if (part.isMimeType("text/*")) {
String enc = part.getEncoding();
if (enc != null && (enc.equalsIgnoreCase("quoted-printable") ||
enc.equalsIgnoreCase("base64"))) {
InputStream is = part.getInputStream();
if (is8Bit(is)) {
/*
* If the message was created using an InputStream
* then we have to extract the content as an object
* and set it back as an object so that the content
* will be re-encoded.
*
* If the message was not created using an InputStream,
* the following should have no effect.
*/
part.setContent(part.getContent(),
part.getContentType());
part.setHeader("Content-Transfer-Encoding", "8bit");
changed = true;
}
}
} else if (part.isMimeType("multipart/*")) {
MimeMultipart mp = (MimeMultipart)part.getContent();
int count = mp.getCount();
for (int i = 0; i < count; i++) {
if (convertTo8Bit((MimePart)mp.getBodyPart(i)))
changed = true;
}
}
} catch (IOException ioex) {
// any exception causes us to give up
} catch (MessagingException mex) {
// any exception causes us to give up
}
return changed;
}
/**
* Check whether the data in the given InputStream follows the
* rules for 8bit text. Lines have to be 998 characters or less
* and no NULs are allowed. CR and LF must occur in pairs but we
* don't check that because we assume this is text and we convert
* all CR/LF combinations into canonical CRLF later.
*/
private boolean is8Bit(InputStream is) {
int b;
int linelen = 0;
boolean need8bit = false;
try {
while ((b = is.read()) >= 0) {
b &= 0xff;
if (b == '\r' || b == '\n')
linelen = 0;
else if (b == 0)
return false;
else {
linelen++;
if (linelen > 998) // 1000 - CRLF
return false;
}
if (b > 0x7f)
need8bit = true;
}
} catch (IOException ex) {
return false;
}
if (debug && need8bit)
out.println("DEBUG SMTP: found an 8bit part");
return need8bit;
}
protected void finalize() throws Throwable {
super.finalize();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -