Update SMTP to send from provider messages, and attachments
* Change Sender definition (remove old Message from API) and update any existing calls through that API * Rewrite SMTPSender to use provider messages * Add attachments to RFC822Output * Minor bugfixes in RFC822Output * Unit tests
This commit is contained in:
parent
b37b1b4bfe
commit
c640cbbaf3
@ -1741,14 +1741,14 @@ public class MessagingController implements Runnable {
|
||||
account.getStoreUri(mContext), mContext, localStore.getPersistentCallbacks());
|
||||
boolean requireCopyMessageToSentFolder = remoteStore.requireCopyMessageToSentFolder();
|
||||
|
||||
Sender sender = Sender.getInstance(account.getSenderUri(mContext), mContext);
|
||||
Sender sender = Sender.getInstance(mContext, account.getSenderUri(mContext));
|
||||
for (Message message : localMessages) {
|
||||
try {
|
||||
localFolder.fetch(new Message[] { message }, fp, null);
|
||||
try {
|
||||
// Send message using Sender
|
||||
message.setFlag(Flag.X_SEND_IN_PROGRESS, true);
|
||||
sender.sendMessage(message);
|
||||
// sender.sendMessage(message);
|
||||
message.setFlag(Flag.X_SEND_IN_PROGRESS, false);
|
||||
|
||||
// Upload to "sent" folder if not supported server-side
|
||||
|
@ -186,7 +186,7 @@ public class AccountSettings extends PreferenceActivity {
|
||||
Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING);
|
||||
boolean showOutgoing = true;
|
||||
try {
|
||||
Sender sender = Sender.getInstance(mAccount.getSenderUri(this), getApplication());
|
||||
Sender sender = Sender.getInstance(getApplication(), mAccount.getSenderUri(this));
|
||||
if (sender != null) {
|
||||
Class<? extends android.app.Activity> setting = sender.getSettingActivityClass();
|
||||
showOutgoing = (setting != null);
|
||||
@ -283,7 +283,7 @@ public class AccountSettings extends PreferenceActivity {
|
||||
|
||||
private void onOutgoingSettings() {
|
||||
try {
|
||||
Sender sender = Sender.getInstance(mAccount.getSenderUri(this), getApplication());
|
||||
Sender sender = Sender.getInstance(getApplication(), mAccount.getSenderUri(this));
|
||||
if (sender != null) {
|
||||
Class<? extends android.app.Activity> setting = sender.getSettingActivityClass();
|
||||
if (setting != null) {
|
||||
|
@ -126,9 +126,8 @@ public class AccountSetupCheckSettings extends Activity implements OnClickListen
|
||||
}
|
||||
if (mCheckOutgoing) {
|
||||
setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
|
||||
Sender sender = Sender.getInstance(
|
||||
mAccount.getSenderUri(AccountSetupCheckSettings.this),
|
||||
getApplication());
|
||||
Sender sender = Sender.getInstance(getApplication(),
|
||||
mAccount.getSenderUri(AccountSetupCheckSettings.this));
|
||||
sender.close();
|
||||
sender.open();
|
||||
sender.close();
|
||||
|
@ -37,12 +37,12 @@ public abstract class Sender {
|
||||
* Static named constructor. It should be overrode by extending class.
|
||||
* Because this method will be called through reflection, it can not be protected.
|
||||
*/
|
||||
public static Sender newInstance(String uri, Context context)
|
||||
public static Sender newInstance(Context context, String uri)
|
||||
throws MessagingException {
|
||||
throw new MessagingException("Sender.newInstance: Unknown scheme in " + uri);
|
||||
}
|
||||
|
||||
private static Sender instantiateSender(String className, String uri, Context context)
|
||||
private static Sender instantiateSender(Context context, String className, String uri)
|
||||
throws MessagingException {
|
||||
Object o = null;
|
||||
try {
|
||||
@ -67,7 +67,7 @@ public abstract class Sender {
|
||||
/**
|
||||
* Find Sender implementation consulting with sender.xml file.
|
||||
*/
|
||||
private static Sender findSender(int resourceId, String uri, Context context)
|
||||
private static Sender findSender(Context context, int resourceId, String uri)
|
||||
throws MessagingException {
|
||||
Sender sender = null;
|
||||
try {
|
||||
@ -82,7 +82,7 @@ public abstract class Sender {
|
||||
// found sender entry whose scheme is matched with uri.
|
||||
// then load sender class.
|
||||
String className = xml.getAttributeValue(null, "class");
|
||||
sender = instantiateSender(className, uri, context);
|
||||
sender = instantiateSender(context, className, uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,13 +94,13 @@ public abstract class Sender {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public synchronized static Sender getInstance(String uri, Context context)
|
||||
public synchronized static Sender getInstance(Context context, String uri)
|
||||
throws MessagingException {
|
||||
Sender sender = mSenders.get(uri);
|
||||
if (sender == null) {
|
||||
sender = findSender(R.xml.senders_product, uri, context);
|
||||
sender = findSender(context, R.xml.senders_product, uri);
|
||||
if (sender == null) {
|
||||
sender = findSender(R.xml.senders, uri, context);
|
||||
sender = findSender(context, R.xml.senders, uri);
|
||||
}
|
||||
|
||||
if (sender != null) {
|
||||
@ -126,18 +126,17 @@ public abstract class Sender {
|
||||
|
||||
public abstract void open() throws MessagingException;
|
||||
|
||||
public String validateSenderLimit(Message message) {
|
||||
public String validateSenderLimit(long messageId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check message has any limitation of Sender or not.
|
||||
*
|
||||
* @param message the message that will be checked.
|
||||
* @param messageId the message that will be checked.
|
||||
* @throws LimitViolationException
|
||||
*/
|
||||
public void checkSenderLimitation(Message message)
|
||||
throws LimitViolationException, MessagingException {
|
||||
public void checkSenderLimitation(long messageId) throws LimitViolationException {
|
||||
}
|
||||
|
||||
public static class LimitViolationException extends MessagingException {
|
||||
@ -160,7 +159,7 @@ public abstract class Sender {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void sendMessage(Message message) throws MessagingException;
|
||||
public abstract void sendMessage(long messageId) throws MessagingException;
|
||||
|
||||
public abstract void close() throws MessagingException;
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ public class ExchangeSenderExample extends Sender {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Message message) throws MessagingException {
|
||||
public void sendMessage(long messageId) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
@ -17,15 +17,25 @@
|
||||
package com.android.email.mail.transport;
|
||||
|
||||
import com.android.email.codec.binary.Base64;
|
||||
import com.android.email.codec.binary.Base64OutputStream;
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
import com.android.email.provider.EmailContent.Body;
|
||||
import com.android.email.provider.EmailContent.Message;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
@ -55,7 +65,7 @@ public class Rfc822Output {
|
||||
* TODO alternative parts (e.g. text+html) are not supported here.
|
||||
*/
|
||||
public static void writeTo(Context context, long messageId, OutputStream out)
|
||||
throws IOException {
|
||||
throws IOException, MessagingException {
|
||||
Message message = Message.restoreMessageWithId(context, messageId);
|
||||
if (message == null) {
|
||||
// throw something?
|
||||
@ -82,39 +92,96 @@ public class Rfc822Output {
|
||||
writeAddressHeader(writer, "Reply-To", message.mReplyTo);
|
||||
|
||||
// Analyze message and determine if we have multiparts
|
||||
// TODO count attachments
|
||||
boolean mixedParts = false;
|
||||
String mixedBoundary = null;
|
||||
String text = Body.restoreBodyTextWithMessageId(context, messageId);
|
||||
|
||||
// Simplified case for no multipart - just emit text and be done.
|
||||
if (!mixedParts) {
|
||||
String text = Body.restoreBodyTextWithMessageId(context, messageId);
|
||||
writeTextWithHeaders(writer, stream, text);
|
||||
} else {
|
||||
// continue with multipart headers, then into multipart body
|
||||
writeHeader(writer, "MIME-Version", "1.0");
|
||||
Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, messageId);
|
||||
Cursor attachmentsCursor = context.getContentResolver().query(uri,
|
||||
Attachment.CONTENT_PROJECTION, null, null, null);
|
||||
|
||||
mixedBoundary = "--_com.android.email_" + System.nanoTime();
|
||||
writeHeader(writer, "Content-Type",
|
||||
"multipart/mixed; boundary=\"" + mixedBoundary + "\"");
|
||||
try {
|
||||
boolean mixedParts = attachmentsCursor.getCount() > 0;
|
||||
String mixedBoundary = null;
|
||||
|
||||
// Finish headers and prepare for body section(s)
|
||||
writer.write("\r\n");
|
||||
// Simplified case for no multipart - just emit text and be done.
|
||||
if (!mixedParts) {
|
||||
if (text != null) {
|
||||
writeTextWithHeaders(writer, stream, text);
|
||||
} else {
|
||||
writer.write("\r\n"); // a truly empty message
|
||||
}
|
||||
} else {
|
||||
// continue with multipart headers, then into multipart body
|
||||
writeHeader(writer, "MIME-Version", "1.0");
|
||||
|
||||
// first multipart element is the body
|
||||
String text = Body.restoreBodyTextWithMessageId(context, messageId);
|
||||
writeTextWithHeaders(writer, stream, text);
|
||||
mixedBoundary = "--_com.android.email_" + System.nanoTime();
|
||||
writeHeader(writer, "Content-Type",
|
||||
"multipart/mixed; boundary=\"" + mixedBoundary + "\"");
|
||||
|
||||
// TODO: attachments here
|
||||
// Finish headers and prepare for body section(s)
|
||||
writer.write("\r\n");
|
||||
|
||||
// end of multipart section
|
||||
writeBoundary(writer, mixedBoundary, true);
|
||||
// first multipart element is the body
|
||||
if (text != null) {
|
||||
writeBoundary(writer, mixedBoundary, false);
|
||||
writeTextWithHeaders(writer, stream, text);
|
||||
}
|
||||
|
||||
// Write out the attachments
|
||||
while (attachmentsCursor.moveToNext()) {
|
||||
writeBoundary(writer, mixedBoundary, false);
|
||||
Attachment attachment =
|
||||
Attachment.getContent(attachmentsCursor, Attachment.class);
|
||||
writeOneAttachment(context, writer, stream, attachment);
|
||||
writer.write("\r\n");
|
||||
}
|
||||
|
||||
// end of multipart section
|
||||
writeBoundary(writer, mixedBoundary, true);
|
||||
}
|
||||
} finally {
|
||||
attachmentsCursor.close();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a single attachment and its payload
|
||||
*/
|
||||
private static void writeOneAttachment(Context context, Writer writer, OutputStream out,
|
||||
Attachment attachment) throws IOException, MessagingException {
|
||||
writeHeader(writer, "Content-Type",
|
||||
attachment.mMimeType + ";\n name=\"" + attachment.mFileName + "\"");
|
||||
writeHeader(writer, "Content-Transfer-Encoding", "base64");
|
||||
writeHeader(writer, "Content-Disposition",
|
||||
"attachment;"
|
||||
+ "\n filename=\"" + attachment.mFileName + "\";"
|
||||
+ "\n size=" + Long.toString(attachment.mSize));
|
||||
writeHeader(writer, "Content-ID", attachment.mContentId);
|
||||
writer.append("\r\n");
|
||||
|
||||
// Set up input stream and write it out via base64
|
||||
InputStream inStream = null;
|
||||
try {
|
||||
// try to open the file
|
||||
Uri fileUri = Uri.parse(attachment.mContentUri);
|
||||
inStream = context.getContentResolver().openInputStream(fileUri);
|
||||
// switch to output stream for base64 text output
|
||||
writer.flush();
|
||||
Base64OutputStream base64Out = new Base64OutputStream(out);
|
||||
// copy base64 data and close up
|
||||
IOUtils.copy(inStream, base64Out);
|
||||
base64Out.close();
|
||||
}
|
||||
catch (FileNotFoundException fnfe) {
|
||||
// Ignore this - empty file is OK
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
throw new MessagingException("Invalid attachment.", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a single header with no wrapping or encoding
|
||||
*
|
||||
@ -198,10 +265,8 @@ public class Rfc822Output {
|
||||
writeHeader(writer, "Content-Type", "text/plain; charset=utf-8");
|
||||
writeHeader(writer, "Content-Transfer-Encoding", "base64");
|
||||
writer.write("\r\n");
|
||||
|
||||
byte[] bytes = text.getBytes("UTF-8");
|
||||
writer.flush();
|
||||
out.write(Base64.encodeBase64Chunked(bytes));
|
||||
writer.write("\r\n");
|
||||
}
|
||||
}
|
||||
|
@ -21,17 +21,15 @@ import com.android.email.codec.binary.Base64;
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.AuthenticationFailedException;
|
||||
import com.android.email.mail.CertificateValidationException;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Sender;
|
||||
import com.android.email.mail.Transport;
|
||||
import com.android.email.mail.Message.RecipientType;
|
||||
import com.android.email.provider.EmailContent.Message;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Config;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
@ -50,6 +48,7 @@ public class SmtpSender extends Sender {
|
||||
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
|
||||
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
|
||||
|
||||
Context mContext;
|
||||
private Transport mTransport;
|
||||
String mUsername;
|
||||
String mPassword;
|
||||
@ -57,8 +56,8 @@ public class SmtpSender extends Sender {
|
||||
/**
|
||||
* Static named constructor.
|
||||
*/
|
||||
public static Sender newInstance(String uri, Context context) throws MessagingException {
|
||||
return new SmtpSender(uri);
|
||||
public static Sender newInstance(Context context, String uri) throws MessagingException {
|
||||
return new SmtpSender(context, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,7 +70,8 @@ public class SmtpSender extends Sender {
|
||||
*
|
||||
* @param uriString the Uri containing information to configure this sender
|
||||
*/
|
||||
private SmtpSender(String uriString) throws MessagingException {
|
||||
private SmtpSender(Context context, String uriString) throws MessagingException {
|
||||
mContext = context;
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(uriString);
|
||||
@ -123,6 +123,7 @@ public class SmtpSender extends Sender {
|
||||
mTransport = testTransport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() throws MessagingException {
|
||||
try {
|
||||
mTransport.open();
|
||||
@ -202,33 +203,36 @@ public class SmtpSender extends Sender {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessage(Message message) throws MessagingException {
|
||||
@Override
|
||||
public void sendMessage(long messageId) throws MessagingException {
|
||||
close();
|
||||
open();
|
||||
Address[] from = message.getFrom();
|
||||
|
||||
Message message = Message.restoreMessageWithId(mContext, messageId);
|
||||
if (message == null) {
|
||||
throw new MessagingException("Trying to send non-existent message id="
|
||||
+ Long.toString(messageId));
|
||||
}
|
||||
Address from = Address.unpackFirst(message.mFrom);
|
||||
Address[] to = Address.unpack(message.mTo);
|
||||
Address[] cc = Address.unpack(message.mCc);
|
||||
Address[] bcc = Address.unpack(message.mBcc);
|
||||
|
||||
try {
|
||||
executeSimpleCommand("MAIL FROM: " + "<" + from[0].getAddress() + ">");
|
||||
for (Address address : message.getRecipients(RecipientType.TO)) {
|
||||
executeSimpleCommand("MAIL FROM: " + "<" + from.getAddress() + ">");
|
||||
for (Address address : to) {
|
||||
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
|
||||
}
|
||||
for (Address address : message.getRecipients(RecipientType.CC)) {
|
||||
for (Address address : cc) {
|
||||
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
|
||||
}
|
||||
for (Address address : message.getRecipients(RecipientType.BCC)) {
|
||||
for (Address address : 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)));
|
||||
Rfc822Output.writeTo(mContext, messageId,
|
||||
new EOLConvertingOutputStream(mTransport.getOutputStream()));
|
||||
executeSimpleCommand("\r\n.");
|
||||
} catch (IOException ioe) {
|
||||
throw new MessagingException("Unable to send message", ioe);
|
||||
@ -240,6 +244,7 @@ public class SmtpSender extends Sender {
|
||||
*
|
||||
* MUST NOT return any exceptions.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
mTransport.close();
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package com.android.exchange;
|
||||
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.transport.Rfc822Output;
|
||||
import com.android.email.provider.EmailContent.Mailbox;
|
||||
import com.android.email.provider.EmailContent.Message;
|
||||
@ -57,7 +58,7 @@ public class EasOutboxService extends EasSyncService {
|
||||
* @param msgId the _id of the message to send
|
||||
* @throws IOException
|
||||
*/
|
||||
void sendMessage(File cacheDir, long msgId) throws IOException {
|
||||
void sendMessage(File cacheDir, long msgId) throws IOException, MessagingException {
|
||||
File tmpFile = File.createTempFile("eas_", "tmp", cacheDir);
|
||||
// Write the output to a temporary file
|
||||
try {
|
||||
|
@ -23,7 +23,6 @@ import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -85,7 +84,15 @@ public class MockTransport implements Transport {
|
||||
}
|
||||
|
||||
private ArrayList<Transaction> mPairs = new ArrayList<Transaction>();
|
||||
|
||||
|
||||
/**
|
||||
* Give the mock a pattern to wait for. No response will be sent.
|
||||
* @param pattern Java RegEx to wait for
|
||||
*/
|
||||
public void expect(String pattern) {
|
||||
expect(pattern, (String[])null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the mock a pattern to wait for and a response to send back.
|
||||
* @param pattern Java RegEx to wait for
|
||||
@ -316,16 +323,14 @@ public class MockTransport implements Transport {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void write(int oneByte) throws IOException {
|
||||
switch (oneByte) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
if (sb.length() > 0) {
|
||||
writeLine(sb.toString(), null);
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
public void write(int oneByte) {
|
||||
// CR or CRLF will immediately dump previous line (w/o CRLF)
|
||||
if (oneByte == '\r') {
|
||||
writeLine(sb.toString(), null);
|
||||
sb = new StringBuilder();
|
||||
} else if (oneByte == '\n') {
|
||||
// swallow it
|
||||
} else {
|
||||
sb.append((char)oneByte);
|
||||
}
|
||||
}
|
||||
|
@ -19,34 +19,61 @@ package com.android.email.mail.transport;
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Transport;
|
||||
import com.android.email.mail.Message.RecipientType;
|
||||
import com.android.email.mail.internet.MimeMessage;
|
||||
import com.android.email.provider.EmailProvider;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
import com.android.email.provider.EmailContent.Body;
|
||||
import com.android.email.provider.EmailContent.Message;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.ProviderTestCase2;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import java.util.Date;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* This is a series of unit tests for the SMTP Sender class. These tests must be locally
|
||||
* complete - no server(s) required.
|
||||
*
|
||||
* These tests can be run with the following command:
|
||||
* runtest -c com.android.email.mail.transport.SmtpSenderUnitTests email
|
||||
*/
|
||||
@SmallTest
|
||||
public class SmtpSenderUnitTests extends AndroidTestCase {
|
||||
public class SmtpSenderUnitTests extends ProviderTestCase2<EmailProvider> {
|
||||
|
||||
EmailProvider mProvider;
|
||||
Context mProviderContext;
|
||||
Context mContext;
|
||||
|
||||
/* These values are provided by setUp() */
|
||||
private SmtpSender mSender = null;
|
||||
|
||||
|
||||
/* Simple test string and its base64 equivalent */
|
||||
private final static String TEST_STRING = "Hello, world";
|
||||
private final static String TEST_STRING_BASE64 = "SGVsbG8sIHdvcmxk";
|
||||
|
||||
public SmtpSenderUnitTests() {
|
||||
super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup code. We generate a lightweight SmtpSender for testing.
|
||||
*/
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mProviderContext = getMockContext();
|
||||
mContext = getContext();
|
||||
|
||||
// These are needed so we can get at the inner classes
|
||||
mSender = (SmtpSender) SmtpSender.newInstance("smtp://user:password@server:999",
|
||||
getContext());
|
||||
mSender = (SmtpSender) SmtpSender.newInstance(mProviderContext,
|
||||
"smtp://user:password@server:999");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,47 +99,252 @@ public class SmtpSenderUnitTests extends AndroidTestCase {
|
||||
|
||||
/**
|
||||
* Test: Open and send a single message (sunny day)
|
||||
*
|
||||
* Note: The final expect (for the ".") is a bit awkward because SmtpSender transmits the
|
||||
* final line as "\r\n." instead of "" and ".".
|
||||
*/
|
||||
public void testSendSingleMessage() throws MessagingException {
|
||||
public void testSendMessageWithBody() throws MessagingException {
|
||||
MockTransport mockTransport = openAndInjectMockTransport();
|
||||
|
||||
|
||||
// Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
|
||||
mockTransport.expectClose();
|
||||
setupOpen(mockTransport, null);
|
||||
|
||||
// prepare and send a really simple message
|
||||
MimeMessage message = new MimeMessage();
|
||||
// TODO use a fixed date for these tests
|
||||
message.setSentDate(new Date());
|
||||
Address from = new Address("Jones@Registry.Org", null);
|
||||
Address to = new Address("Smith@Registry.Org", null);
|
||||
message.setFrom(from);
|
||||
message.setRecipients(RecipientType.TO, new Address[] { to });
|
||||
|
||||
Message message = setupSimpleMessage();
|
||||
message.save(mProviderContext);
|
||||
|
||||
Body body = new Body();
|
||||
body.mMessageKey = message.mId;
|
||||
body.mTextContent = TEST_STRING;
|
||||
body.save(mProviderContext);
|
||||
|
||||
// prepare for the message traffic we'll see
|
||||
// TODO We should have a method to do this for any Message
|
||||
// TODO The test is a bit fragile, as we are order-dependent (and headers are not)
|
||||
expectSimpleMessage(mockTransport);
|
||||
mockTransport.expect("Content-Type: text/plain; charset=utf-8");
|
||||
mockTransport.expect("Content-Transfer-Encoding: base64");
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect(TEST_STRING_BASE64);
|
||||
mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");
|
||||
|
||||
// Now trigger the transmission
|
||||
mSender.sendMessage(message.mId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Open and send a single message with an empty attachment (no file) (sunny day)
|
||||
*/
|
||||
public void testSendMessageWithEmptyAttachment() throws MessagingException, IOException {
|
||||
MockTransport mockTransport = openAndInjectMockTransport();
|
||||
|
||||
// Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
|
||||
mockTransport.expectClose();
|
||||
setupOpen(mockTransport, null);
|
||||
|
||||
Message message = setupSimpleMessage();
|
||||
message.save(mProviderContext);
|
||||
|
||||
// Creates an attachment with a bogus file (so we get headers only)
|
||||
Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, false);
|
||||
attachment.save(mProviderContext);
|
||||
|
||||
expectSimpleMessage(mockTransport);
|
||||
mockTransport.expect("MIME-Version: 1.0");
|
||||
mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*");
|
||||
expectSimpleAttachment(mockTransport, attachment);
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*--");
|
||||
mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");
|
||||
|
||||
// Now trigger the transmission
|
||||
mSender.sendMessage(message.mId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Open and send a single message with an attachment (sunny day)
|
||||
*/
|
||||
public void testSendMessageWithAttachment() throws MessagingException, IOException {
|
||||
MockTransport mockTransport = openAndInjectMockTransport();
|
||||
|
||||
// Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
|
||||
mockTransport.expectClose();
|
||||
setupOpen(mockTransport, null);
|
||||
|
||||
Message message = setupSimpleMessage();
|
||||
message.save(mProviderContext);
|
||||
|
||||
// Creates an attachment with a real file
|
||||
Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true);
|
||||
attachment.save(mProviderContext);
|
||||
|
||||
expectSimpleMessage(mockTransport);
|
||||
mockTransport.expect("MIME-Version: 1.0");
|
||||
mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*");
|
||||
expectSimpleAttachment(mockTransport, attachment);
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*--");
|
||||
mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");
|
||||
|
||||
// Now trigger the transmission
|
||||
mSender.sendMessage(message.mId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Open and send a single message with two attachments
|
||||
*/
|
||||
public void testSendMessageWithTwoAttachments() throws MessagingException, IOException {
|
||||
MockTransport mockTransport = openAndInjectMockTransport();
|
||||
|
||||
// Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
|
||||
mockTransport.expectClose();
|
||||
setupOpen(mockTransport, null);
|
||||
|
||||
Message message = setupSimpleMessage();
|
||||
message.save(mProviderContext);
|
||||
|
||||
// Creates an attachment with a real file
|
||||
Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true);
|
||||
attachment.save(mProviderContext);
|
||||
|
||||
// Creates an attachment with a real file
|
||||
Attachment attachment2 = setupSimpleAttachment(mProviderContext, message.mId, true);
|
||||
attachment2.save(mProviderContext);
|
||||
|
||||
expectSimpleMessage(mockTransport);
|
||||
mockTransport.expect("MIME-Version: 1.0");
|
||||
mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*");
|
||||
expectSimpleAttachment(mockTransport, attachment);
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*");
|
||||
expectSimpleAttachment(mockTransport, attachment2);
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*--");
|
||||
mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");
|
||||
|
||||
// Now trigger the transmission
|
||||
mSender.sendMessage(message.mId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Open and send a single message with body & attachment (sunny day)
|
||||
*/
|
||||
public void testSendMessageWithBodyAndAttachment() throws MessagingException, IOException {
|
||||
MockTransport mockTransport = openAndInjectMockTransport();
|
||||
|
||||
// Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
|
||||
mockTransport.expectClose();
|
||||
setupOpen(mockTransport, null);
|
||||
|
||||
Message message = setupSimpleMessage();
|
||||
message.save(mProviderContext);
|
||||
|
||||
Body body = new Body();
|
||||
body.mMessageKey = message.mId;
|
||||
body.mTextContent = TEST_STRING;
|
||||
body.save(mProviderContext);
|
||||
|
||||
Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true);
|
||||
attachment.save(mProviderContext);
|
||||
|
||||
// prepare for the message traffic we'll see
|
||||
expectSimpleMessage(mockTransport);
|
||||
mockTransport.expect("MIME-Version: 1.0");
|
||||
mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*");
|
||||
mockTransport.expect("Content-Type: text/plain; charset=utf-8");
|
||||
mockTransport.expect("Content-Transfer-Encoding: base64");
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect(TEST_STRING_BASE64);
|
||||
mockTransport.expect("----.*");
|
||||
expectSimpleAttachment(mockTransport, attachment);
|
||||
mockTransport.expect("");
|
||||
mockTransport.expect("----.*--");
|
||||
mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");
|
||||
|
||||
// Now trigger the transmission
|
||||
mSender.sendMessage(message.mId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to send a simple message (see setReceiveSimpleMessage)
|
||||
*/
|
||||
private Message setupSimpleMessage() {
|
||||
Message message = new Message();
|
||||
message.mTimeStamp = System.currentTimeMillis();
|
||||
message.mFrom = Address.parseAndPack("Jones@Registry.Org");
|
||||
message.mTo = Address.parseAndPack("Smith@Registry.Org");
|
||||
message.mMessageId = "1234567890";
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to receive a simple message (see setupSimpleMessage)
|
||||
*/
|
||||
private void expectSimpleMessage(MockTransport mockTransport) {
|
||||
mockTransport.expect("MAIL FROM: <Jones@Registry.Org>",
|
||||
"250 2.1.0 <Jones@Registry.Org> sender ok");
|
||||
mockTransport.expect("RCPT TO: <Smith@Registry.Org>",
|
||||
"250 2.1.5 <Smith@Registry.Org> recipient ok");
|
||||
mockTransport.expect("DATA", "354 enter mail, end with . on a line by itself");
|
||||
mockTransport.expect("Message-ID: .*", (String)null);
|
||||
mockTransport.expect("Date: .*", (String)null);
|
||||
mockTransport.expect("From: Jones@Registry.Org", (String)null);
|
||||
mockTransport.expect("To: Smith@Registry.Org", (String)null);
|
||||
mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");
|
||||
|
||||
// Now trigger the transmission
|
||||
mSender.sendMessage(message);
|
||||
mockTransport.expect("Date: .*");
|
||||
mockTransport.expect("Message-ID: .*");
|
||||
mockTransport.expect("From: Jones@Registry.Org");
|
||||
mockTransport.expect("To: Smith@Registry.Org");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare to send a simple attachment
|
||||
*/
|
||||
private Attachment setupSimpleAttachment(Context context, long messageId, boolean withBody)
|
||||
throws IOException {
|
||||
Attachment attachment = new Attachment();
|
||||
attachment.mFileName = "the file.jpg";
|
||||
attachment.mMimeType = "image/jpg";
|
||||
attachment.mSize = 0;
|
||||
attachment.mContentId = null;
|
||||
attachment.mContentUri = "content://com.android.email/1/1";
|
||||
attachment.mMessageKey = messageId;
|
||||
attachment.mLocation = null;
|
||||
attachment.mEncoding = null;
|
||||
|
||||
if (withBody) {
|
||||
// Is there an easier way to set up a temp file?
|
||||
InputStream inStream = new ByteArrayInputStream(TEST_STRING.getBytes());
|
||||
File cacheDir = context.getCacheDir();
|
||||
File tmpFile = File.createTempFile("setupSimpleAttachment", "tmp", cacheDir);
|
||||
OutputStream outStream = new FileOutputStream(tmpFile);
|
||||
|
||||
IOUtils.copy(inStream, outStream);
|
||||
attachment.mContentUri = "file://" + tmpFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
return attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to receive a simple attachment (note, no multipart support here)
|
||||
*/
|
||||
private void expectSimpleAttachment(MockTransport mockTransport, Attachment attachment) {
|
||||
mockTransport.expect("Content-Type: " + attachment.mMimeType + ";");
|
||||
mockTransport.expect(" name=\"" + attachment.mFileName + "\"");
|
||||
mockTransport.expect("Content-Transfer-Encoding: base64");
|
||||
mockTransport.expect("Content-Disposition: attachment;");
|
||||
mockTransport.expect(" filename=\"" + attachment.mFileName + "\";");
|
||||
mockTransport.expect(" size=" + Long.toString(attachment.mSize));
|
||||
mockTransport.expect("");
|
||||
if (attachment.mContentUri != null && attachment.mContentUri.startsWith("file://")) {
|
||||
mockTransport.expect(TEST_STRING_BASE64);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Recover from a server closing early (or returning an empty string)
|
||||
*/
|
||||
public void testEmptyLineResponse() throws MessagingException {
|
||||
public void testEmptyLineResponse() {
|
||||
MockTransport mockTransport = openAndInjectMockTransport();
|
||||
|
||||
// Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
|
||||
@ -125,7 +357,7 @@ public class SmtpSenderUnitTests extends AndroidTestCase {
|
||||
// Now trigger the transmission
|
||||
// Note, a null message is sufficient here, as we won't even get past open()
|
||||
try {
|
||||
mSender.sendMessage(null);
|
||||
mSender.sendMessage(-1);
|
||||
fail("Should not be able to send with failed open()");
|
||||
} catch (MessagingException me) {
|
||||
// good - expected
|
||||
|
Loading…
Reference in New Issue
Block a user