1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.emailcommon.internet; 18 19 import android.content.Context; 20 import android.test.ProviderTestCase2; 21 import android.test.suitebuilder.annotation.Suppress; 22 23 import com.android.email.R; 24 import com.android.email.provider.EmailProvider; 25 import com.android.emailcommon.mail.MessagingException; 26 import com.android.emailcommon.provider.EmailContent; 27 import com.android.emailcommon.provider.EmailContent.Attachment; 28 import com.android.emailcommon.provider.EmailContent.Body; 29 import com.android.emailcommon.provider.EmailContent.Message; 30 31 import org.apache.james.mime4j.field.Field; 32 import org.apache.james.mime4j.message.BodyPart; 33 import org.apache.james.mime4j.message.Entity; 34 import org.apache.james.mime4j.message.Header; 35 import org.apache.james.mime4j.message.Multipart; 36 37 import java.io.ByteArrayInputStream; 38 import java.io.ByteArrayOutputStream; 39 import java.io.IOException; 40 import java.util.ArrayList; 41 import java.util.List; 42 43 44 /** 45 * Tests of the Rfc822Output (used for sending mail) 46 * 47 * You can run this entire test case with: 48 * runtest -c com.android.email.mail.transport.Rfc822OutputTests email 49 */ 50 @Suppress 51 public class Rfc822OutputTests extends ProviderTestCase2<EmailProvider> { 52 private static final String SENDER = "sender (at) android.com"; 53 private static final String RECIPIENT_TO = "recipient-to (at) android.com"; 54 private static final String RECIPIENT_CC = "recipient-cc (at) android.com"; 55 private static final String SUBJECT = "This is the subject"; 56 private static final String REPLY_TEXT_BODY = "This is the body. This is also the body."; 57 private static final String TEXT = "Here is some new text."; 58 59 // Full HTML document 60 private static final String HTML_FULL_BODY = "<html><head><title>MyTitle</title></head>" 61 + "<body bgcolor=\"#ffffff\" text=\"#000000\">" 62 + "<a href=\"google.com\">test1</a></body></html>"; 63 private static final String HTML_FULL_RESULT = "<a href=\"google.com\">test1</a>"; 64 // <body/> element w/ content 65 private static final String HTML_BODY_BODY = 66 "<body bgcolor=\"#ffffff\" text=\"#000000\"><a href=\"google.com\">test2</a></body>"; 67 private static final String HTML_BODY_RESULT = "<a href=\"google.com\">test2</a>"; 68 // No <body/> tag; just content 69 private static final String HTML_NO_BODY_BODY = 70 "<a href=\"google.com\">test3</a>"; 71 private static final String HTML_NO_BODY_RESULT = "<a href=\"google.com\">test3</a>"; 72 73 private Context mMockContext; 74 private String mForwardIntro; 75 76 public Rfc822OutputTests () { 77 super(EmailProvider.class, EmailContent.AUTHORITY); 78 } 79 80 @Override 81 public void setUp() throws Exception { 82 super.setUp(); 83 mMockContext = getMockContext(); 84 mForwardIntro = mMockContext.getString(R.string.message_compose_fwd_header_fmt, SUBJECT, 85 SENDER, RECIPIENT_TO, RECIPIENT_CC); 86 } 87 88 // TODO Create more tests here. Specifically, we should test to make sure that forward works 89 // properly instead of just reply 90 91 // TODO Write test that ensures that bcc is handled properly (i.e. sent/not send depending 92 // on the flag passed to writeTo 93 94 private Body createTestBody(Message message) { 95 return Body.restoreBodyWithMessageId(mMockContext, message.mId); 96 } 97 98 /** 99 * Test for buildBodyText(). 100 * Compare with expected values. 101 */ 102 public void testBuildBodyTextWithForward() { 103 Message msg = new Message(); 104 msg.mText = TEXT; 105 msg.mFrom = SENDER; 106 msg.mTo = RECIPIENT_TO; 107 msg.mCc = RECIPIENT_CC; 108 msg.mSubject = SUBJECT; 109 msg.mFlags = Message.FLAG_TYPE_FORWARD; 110 msg.save(mMockContext); 111 Body body = createTestBody(msg); 112 String[] bodyParts = Rfc822Output.buildBodyText(body, false); 113 assertEquals(TEXT + mForwardIntro + REPLY_TEXT_BODY, bodyParts[0]); 114 } 115 116 public void testWriteToText() throws IOException, MessagingException { 117 // Create a simple text message 118 Message msg = new Message(); 119 msg.mText = TEXT; 120 msg.mFrom = SENDER; 121 // Save this away 122 msg.save(mMockContext); 123 124 // Write out an Rfc822 message 125 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 126 Rfc822Output.writeTo(mMockContext, msg, byteStream, true, false, null); 127 128 // Get the message and create a mime4j message from it 129 // We'll take advantage of its parsing capabilities 130 ByteArrayInputStream messageInputStream = 131 new ByteArrayInputStream(byteStream.toByteArray()); 132 org.apache.james.mime4j.message.Message mimeMessage = 133 new org.apache.james.mime4j.message.Message(messageInputStream); 134 135 // Make sure its structure is correct 136 checkMimeVersion(mimeMessage); 137 assertFalse(mimeMessage.isMultipart()); 138 assertEquals("text/plain", mimeMessage.getMimeType()); 139 } 140 141 @SuppressWarnings("unchecked") 142 public void testWriteToAlternativePart() throws IOException, MessagingException { 143 // Create a message with alternative part 144 Message msg = new Message(); 145 msg.mText = TEXT; 146 msg.mFrom = SENDER; 147 msg.mAttachments = new ArrayList<Attachment>(); 148 // Attach a meeting invitation, which needs to be sent as multipart/alternative 149 Attachment att = new Attachment(); 150 att.mContentBytes = "__CONTENT__".getBytes("UTF-8"); 151 att.mFlags = Attachment.FLAG_ICS_ALTERNATIVE_PART; 152 att.mMimeType = "text/calendar"; 153 att.mFileName = "invite.ics"; 154 msg.mAttachments.add(att); 155 // Save this away 156 msg.save(mMockContext); 157 158 // Write out an Rfc822 message 159 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 160 Rfc822Output.writeTo(mMockContext, msg, byteStream, true, false, null); 161 162 // Get the message and create a mime4j message from it 163 // We'll take advantage of its parsing capabilities 164 ByteArrayInputStream messageInputStream = 165 new ByteArrayInputStream(byteStream.toByteArray()); 166 org.apache.james.mime4j.message.Message mimeMessage = 167 new org.apache.james.mime4j.message.Message(messageInputStream); 168 169 // Make sure its structure is correct 170 checkMimeVersion(mimeMessage); 171 assertTrue(mimeMessage.isMultipart()); 172 Header header = mimeMessage.getHeader(); 173 Field contentType = header.getField("content-type"); 174 assertTrue(contentType.getBody().contains("multipart/alternative")); 175 Multipart multipart = (Multipart)mimeMessage.getBody(); 176 List<BodyPart> partList = multipart.getBodyParts(); 177 assertEquals(2, partList.size()); 178 Entity part = partList.get(0); 179 assertEquals("text/plain", part.getMimeType()); 180 part = partList.get(1); 181 assertEquals("text/calendar", part.getMimeType()); 182 header = part.getHeader(); 183 assertNull(header.getField("content-disposition")); 184 } 185 186 @SuppressWarnings("unchecked") 187 public void testWriteToMixedPart() throws IOException, MessagingException { 188 // Create a message with a mixed part 189 Message msg = new Message(); 190 msg.mText = TEXT; 191 msg.mFrom = SENDER; 192 msg.mAttachments = new ArrayList<Attachment>(); 193 // Attach a simple html "file" 194 Attachment att = new Attachment(); 195 att.mContentBytes = "<html>Hi</html>".getBytes("UTF-8"); 196 att.mMimeType = "text/html"; 197 att.mFileName = "test.html"; 198 msg.mAttachments.add(att); 199 // Save this away 200 msg.save(mMockContext); 201 202 // Write out an Rfc822 message 203 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 204 Rfc822Output.writeTo(mMockContext, msg, byteStream, true, false, null); 205 206 // Get the message and create a mime4j message from it 207 // We'll take advantage of its parsing capabilities 208 ByteArrayInputStream messageInputStream = 209 new ByteArrayInputStream(byteStream.toByteArray()); 210 org.apache.james.mime4j.message.Message mimeMessage = 211 new org.apache.james.mime4j.message.Message(messageInputStream); 212 213 // Make sure its structure is correct 214 checkMimeVersion(mimeMessage); 215 assertTrue(mimeMessage.isMultipart()); 216 Header header = mimeMessage.getHeader(); 217 Field contentType = header.getField("content-type"); 218 assertTrue(contentType.getBody().contains("multipart/mixed")); 219 Multipart multipart = (Multipart)mimeMessage.getBody(); 220 List<BodyPart> partList = multipart.getBodyParts(); 221 assertEquals(2, partList.size()); 222 Entity part = partList.get(0); 223 assertEquals("text/plain", part.getMimeType()); 224 part = partList.get(1); 225 assertEquals("text/html", part.getMimeType()); 226 header = part.getHeader(); 227 assertNotNull(header.getField("content-disposition")); 228 } 229 230 /** 231 * Tests various types of HTML reply text -- with full <html/> tags, 232 * with just the <body/> tags and without any surrounding tags. 233 */ 234 public void testGetHtmlBody() { 235 String actual; 236 actual = Rfc822Output.getHtmlBody(HTML_FULL_BODY); 237 assertEquals(HTML_FULL_RESULT, actual); 238 actual = Rfc822Output.getHtmlBody(HTML_BODY_BODY); 239 assertEquals(HTML_BODY_RESULT, actual); 240 actual = Rfc822Output.getHtmlBody(HTML_NO_BODY_BODY); 241 assertEquals(HTML_NO_BODY_RESULT, actual); 242 } 243 244 /** 245 * Test the boundary digit. We modify it indirectly. 246 */ 247 public void testBoundaryDigit() { 248 // Use getBoundary() to update the boundary digit 249 Rfc822Output.sBoundaryDigit = 0; // ensure it starts at a known value 250 251 Rfc822Output.getNextBoundary(); 252 assertEquals(1, Rfc822Output.sBoundaryDigit); 253 Rfc822Output.getNextBoundary(); 254 assertEquals(2, Rfc822Output.sBoundaryDigit); 255 Rfc822Output.getNextBoundary(); 256 assertEquals(3, Rfc822Output.sBoundaryDigit); 257 Rfc822Output.getNextBoundary(); 258 assertEquals(4, Rfc822Output.sBoundaryDigit); 259 Rfc822Output.getNextBoundary(); 260 assertEquals(5, Rfc822Output.sBoundaryDigit); 261 Rfc822Output.getNextBoundary(); 262 assertEquals(6, Rfc822Output.sBoundaryDigit); 263 Rfc822Output.getNextBoundary(); 264 assertEquals(7, Rfc822Output.sBoundaryDigit); 265 Rfc822Output.getNextBoundary(); 266 assertEquals(8, Rfc822Output.sBoundaryDigit); 267 Rfc822Output.getNextBoundary(); 268 assertEquals(9, Rfc822Output.sBoundaryDigit); 269 Rfc822Output.getNextBoundary(); // roll over 270 assertEquals(0, Rfc822Output.sBoundaryDigit); 271 } 272 273 private static final int BOUNDARY_COUNT = 12; 274 public void testGetNextBoundary() { 275 String[] resultArray = new String[BOUNDARY_COUNT]; 276 for (int i = 0; i < BOUNDARY_COUNT; i++) { 277 resultArray[i] = Rfc822Output.getNextBoundary(); 278 } 279 for (int i = 0; i < BOUNDARY_COUNT; i++) { 280 final String result1 = resultArray[i]; 281 for (int j = 0; j < BOUNDARY_COUNT; j++) { 282 if (i == j) { 283 continue; // Don't verify the same result 284 } 285 final String result2 = resultArray[j]; 286 assertFalse(result1.equals(result2)); 287 } 288 } 289 } 290 291 /** 292 * Confirm that the constructed message includes "MIME-VERSION: 1.0" 293 */ 294 private void checkMimeVersion(org.apache.james.mime4j.message.Message mimeMessage) { 295 Header header = mimeMessage.getHeader(); 296 Field contentType = header.getField("MIME-VERSION"); 297 assertTrue(contentType.getBody().equals("1.0")); 298 } 299 } 300