409 lines
16 KiB
Java
409 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.email.mail.transport;
|
|
|
|
import com.android.email.DBTestHelper;
|
|
import com.android.email.mail.Transport;
|
|
import com.android.email.provider.EmailProvider;
|
|
import com.android.emailcommon.mail.Address;
|
|
import com.android.emailcommon.mail.MessagingException;
|
|
import com.android.emailcommon.provider.Account;
|
|
import com.android.emailcommon.provider.EmailContent.Attachment;
|
|
import com.android.emailcommon.provider.EmailContent.Body;
|
|
import com.android.emailcommon.provider.EmailContent.Message;
|
|
import com.android.emailcommon.provider.HostAuth;
|
|
|
|
import org.apache.commons.io.IOUtils;
|
|
|
|
import android.content.Context;
|
|
import android.test.AndroidTestCase;
|
|
import android.test.suitebuilder.annotation.SmallTest;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.InetAddress;
|
|
import java.net.UnknownHostException;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* 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 {
|
|
|
|
EmailProvider mProvider;
|
|
Context mProviderContext;
|
|
Context mContext;
|
|
private static final String LOCAL_ADDRESS = "1.2.3.4";
|
|
|
|
/* 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";
|
|
|
|
/**
|
|
* Setup code. We generate a lightweight SmtpSender for testing.
|
|
*/
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
super.setUp();
|
|
mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
|
|
getContext());
|
|
mContext = getContext();
|
|
|
|
HostAuth testAuth = new HostAuth();
|
|
Account testAccount = new Account();
|
|
|
|
testAuth.setLogin("user", "password");
|
|
testAuth.setConnection("smtp", "server", 999);
|
|
testAccount.mHostAuthSend = testAuth;
|
|
mSender = (SmtpSender) SmtpSender.newInstance(testAccount, mProviderContext);
|
|
}
|
|
|
|
/**
|
|
* Confirms simple non-SSL non-TLS login
|
|
*/
|
|
public void testSimpleLogin() throws Exception {
|
|
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
|
|
// try to open it
|
|
setupOpen(mockTransport, null);
|
|
mSender.open();
|
|
}
|
|
|
|
/**
|
|
* TODO: Test with SSL negotiation (faked)
|
|
* TODO: Test with SSL required but not supported
|
|
* TODO: Test with TLS negotiation (faked)
|
|
* TODO: Test with TLS required but not supported
|
|
* TODO: Test other capabilities.
|
|
* TODO: Test AUTH LOGIN
|
|
*/
|
|
|
|
/**
|
|
* Test: Open and send a single message (sunny day)
|
|
*/
|
|
public void testSendMessageWithBody() throws Exception {
|
|
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);
|
|
|
|
// prepare for the message traffic we'll see
|
|
// 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("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("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("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("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("Date: .*");
|
|
mockTransport.expect("Message-ID: .*");
|
|
mockTransport.expect("From: Jones@Registry.Org");
|
|
mockTransport.expect("To: Smith@Registry.Org");
|
|
mockTransport.expect("MIME-Version: 1.0");
|
|
}
|
|
|
|
/**
|
|
* 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 Exception {
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
|
|
// Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
|
|
mockTransport.expectClose();
|
|
|
|
// Load up just the bare minimum to expose the error
|
|
mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
|
|
mockTransport.expect("EHLO " + Pattern.quote(LOCAL_ADDRESS), "");
|
|
|
|
// Now trigger the transmission
|
|
// Note, a null message is sufficient here, as we won't even get past open()
|
|
try {
|
|
mSender.sendMessage(-1);
|
|
fail("Should not be able to send with failed open()");
|
|
} catch (MessagingException me) {
|
|
// good - expected
|
|
// TODO maybe expect a particular exception?
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up a basic MockTransport. open it, and inject it into mStore
|
|
*/
|
|
private MockTransport openAndInjectMockTransport() throws UnknownHostException {
|
|
// Create mock transport and inject it into the SmtpSender that's already set up
|
|
MockTransport mockTransport = new MockTransport();
|
|
mockTransport.setSecurity(Transport.CONNECTION_SECURITY_NONE, false);
|
|
mSender.setTransport(mockTransport);
|
|
mockTransport.setMockLocalAddress(InetAddress.getByName(LOCAL_ADDRESS));
|
|
return mockTransport;
|
|
}
|
|
|
|
/**
|
|
* Helper which stuffs the mock with enough strings to satisfy a call to SmtpSender.open()
|
|
*
|
|
* @param mockTransport the mock transport we're using
|
|
* @param capabilities if non-null, comma-separated list of capabilities
|
|
*/
|
|
private void setupOpen(MockTransport mockTransport, String capabilities) {
|
|
mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
|
|
mockTransport.expect("EHLO .*", "250-10.20.30.40 hello");
|
|
if (capabilities == null) {
|
|
mockTransport.expect(null, "250-HELP");
|
|
mockTransport.expect(null, "250-AUTH LOGIN PLAIN CRAM-MD5");
|
|
mockTransport.expect(null, "250-SIZE 15728640");
|
|
mockTransport.expect(null, "250-ENHANCEDSTATUSCODES");
|
|
mockTransport.expect(null, "250-8BITMIME");
|
|
} else {
|
|
for (String capability : capabilities.split(",")) {
|
|
mockTransport.expect(null, "250-" + capability);
|
|
}
|
|
}
|
|
mockTransport.expect(null, "250+OK");
|
|
mockTransport.expect("AUTH PLAIN .*", "235 2.7.0 ... authentication succeeded");
|
|
}
|
|
}
|