SmtpSenderpublic class SmtpSender extends com.android.email.mail.Sender This class handles all of the protocol-level aspects of sending messages via SMTP. |
Fields Summary |
---|
public static final int | CONNECTION_SECURITY_NONE | public static final int | CONNECTION_SECURITY_TLS_OPTIONAL | public static final int | CONNECTION_SECURITY_TLS_REQUIRED | public static final int | CONNECTION_SECURITY_SSL_REQUIRED | public static final int | CONNECTION_SECURITY_SSL_OPTIONAL | private com.android.email.mail.Transport | mTransport | String | mUsername | String | mPassword |
Constructors Summary |
---|
public SmtpSender(String uriString)Allowed formats for the Uri:
smtp://user:password@server:port CONNECTION_SECURITY_NONE
smtp+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
smtp+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
smtp+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
smtp+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
URI uri;
try {
uri = new URI(uriString);
} catch (URISyntaxException use) {
throw new MessagingException("Invalid SmtpTransport URI", use);
}
String scheme = uri.getScheme();
int connectionSecurity = Transport.CONNECTION_SECURITY_NONE;
int defaultPort = -1;
if (scheme.equals("smtp")) {
connectionSecurity = CONNECTION_SECURITY_NONE;
defaultPort = 25;
} else if (scheme.equals("smtp+tls")) {
connectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL;
defaultPort = 25;
} else if (scheme.equals("smtp+tls+")) {
connectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED;
defaultPort = 25;
} else if (scheme.equals("smtp+ssl+")) {
connectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED;
defaultPort = 465;
} else if (scheme.equals("smtp+ssl")) {
connectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL;
defaultPort = 465;
} else {
throw new MessagingException("Unsupported protocol");
}
mTransport = new MailTransport("SMTP");
mTransport.setUri(uri, defaultPort);
mTransport.setSecurity(connectionSecurity);
String[] userInfoParts = mTransport.getUserInfoParts();
if (userInfoParts != null) {
mUsername = userInfoParts[0];
if (userInfoParts.length > 1) {
mPassword = userInfoParts[1];
}
}
|
Methods Summary |
---|
public void | close()Close the protocol (and the transport below it).
MUST NOT return any exceptions.
mTransport.close();
| private java.lang.String | executeSensitiveCommand(java.lang.String command, java.lang.String sensitiveReplacement)Send a single command and wait for a single response. Handles responses that continue
onto multiple lines. Throws MessagingException if response code is 4xx or 5xx.
if (command != null) {
mTransport.writeLine(command, sensitiveReplacement);
}
String line = mTransport.readLine();
String result = line;
while (line.length() >= 4 && line.charAt(3) == '-") {
line = mTransport.readLine();
result += line.substring(3);
}
char c = result.charAt(0);
if ((c == '4") || (c == '5")) {
throw new MessagingException(result);
}
return result;
| private java.lang.String | executeSimpleCommand(java.lang.String command)Send a single command and wait for a single response. Handles responses that continue
onto multiple lines. Throws MessagingException if response code is 4xx or 5xx. All traffic
is logged (if debug logging is enabled) so do not use this function for user ID or password.
return executeSensitiveCommand(command, null);
| public void | open()
try {
mTransport.open();
// Eat the banner
executeSimpleCommand(null);
String localHost = "localhost";
try {
InetAddress localAddress = InetAddress.getLocalHost();
localHost = localAddress.getHostName();
} catch (Exception e) {
if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, "Unable to look up localhost");
}
}
String result = executeSimpleCommand("EHLO " + localHost);
/*
* TODO may need to add code to fall back to HELO I switched it from
* using HELO on non STARTTLS connections because of AOL's mail
* server. It won't let you use AUTH without EHLO.
* We should really be paying more attention to the capabilities
* and only attempting auth if it's available, and warning the user
* if not.
*/
if (mTransport.canTryTlsSecurity()) {
if (result.contains("-STARTTLS")) {
executeSimpleCommand("STARTTLS");
mTransport.reopenTls();
/*
* Now resend the EHLO. Required by RFC2487 Sec. 5.2, and more specifically,
* Exim.
*/
result = executeSimpleCommand("EHLO " + localHost);
} else if (mTransport.getSecurity() ==
Transport.CONNECTION_SECURITY_TLS_REQUIRED) {
if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, "TLS not supported but required");
}
throw new MessagingException(MessagingException.TLS_REQUIRED);
}
}
/*
* result contains the results of the EHLO in concatenated form
*/
boolean authLoginSupported = result.matches(".*AUTH.*LOGIN.*$");
boolean authPlainSupported = result.matches(".*AUTH.*PLAIN.*$");
if (mUsername != null && mUsername.length() > 0 && mPassword != null
&& mPassword.length() > 0) {
if (authPlainSupported) {
saslAuthPlain(mUsername, mPassword);
}
else if (authLoginSupported) {
saslAuthLogin(mUsername, mPassword);
}
else {
if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, "No valid authentication mechanism found.");
}
throw new MessagingException(MessagingException.AUTH_REQUIRED);
}
}
} catch (SSLException e) {
if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, e.toString());
}
throw new CertificateValidationException(e.getMessage(), e);
} catch (IOException ioe) {
if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, ioe.toString());
}
throw new MessagingException(MessagingException.IOERROR, ioe.toString());
}
| private void | saslAuthLogin(java.lang.String username, java.lang.String password)
try {
executeSimpleCommand("AUTH LOGIN");
executeSensitiveCommand(new String(Base64.encodeBase64(username.getBytes())),
"/username redacted/");
executeSensitiveCommand(new String(Base64.encodeBase64(password.getBytes())),
"/password redacted/");
}
catch (MessagingException me) {
if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3") {
throw new AuthenticationFailedException(me.getMessage());
}
throw me;
}
| private void | saslAuthPlain(java.lang.String username, java.lang.String password)
byte[] data = ("\000" + username + "\000" + password).getBytes();
data = new Base64().encode(data);
try {
executeSensitiveCommand("AUTH PLAIN " + new String(data), "AUTH PLAIN /redacted/");
}
catch (MessagingException me) {
if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3") {
throw new AuthenticationFailedException(me.getMessage());
}
throw me;
}
| public void | sendMessage(com.android.email.mail.Message message)
close();
open();
Address[] from = message.getFrom();
try {
executeSimpleCommand("MAIL FROM: " + "<" + from[0].getAddress() + ">");
for (Address address : message.getRecipients(RecipientType.TO)) {
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
}
for (Address address : message.getRecipients(RecipientType.CC)) {
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
}
for (Address address : message.getRecipients(RecipientType.BCC)) {
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
}
message.setRecipients(RecipientType.BCC, null);
executeSimpleCommand("DATA");
// TODO byte stuffing
// TODO most of the MIME writeTo functions layer on *additional* buffering
// streams, making this one possibly not-necessary. Need to get to the bottom
// of that.
// TODO Also, need to be absolutely positively sure that flush() is called
// on the wrappered outputs before sending the final \r\n via the regular mOut.
message.writeTo(
new EOLConvertingOutputStream(
new BufferedOutputStream(mTransport.getOutputStream(), 1024)));
executeSimpleCommand("\r\n.");
} catch (IOException ioe) {
throw new MessagingException("Unable to send message", ioe);
}
| void | setTransport(com.android.email.mail.Transport testTransport)For testing only. Injects a different transport. The transport should already be set
up and ready to use. Do not use for real code.
mTransport = testTransport;
|
|