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:
Andrew Stadler 2009-08-13 09:31:57 -07:00
parent b37b1b4bfe
commit c640cbbaf3
10 changed files with 418 additions and 112 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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();

View File

@ -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;
}

View File

@ -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
}

View File

@ -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");
}
}

View File

@ -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();
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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