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 /** HTML reply body */ 58 private static final String BODY_HTML_REPLY = 59 "<a href=\"m.google.com\">This</a> is the body.<br>This is also the body."; 60 /** Text-only version of the HTML reply body */ 61 private static final String BODY_TEXT_REPLY_HTML = 62 ">This is the body.\n>This is also the body."; 63 private static final String TEXT = "Here is some new text."; 64 65 // Full HTML document 66 private static String HTML_FULL_BODY = "<html><head><title>MyTitle</title></head>" 67 + "<body bgcolor=\"#ffffff\" text=\"#000000\">" 68 + "<a href=\"google.com\">test1</a></body></html>"; 69 private static String HTML_FULL_RESULT = "<a href=\"google.com\">test1</a>"; 70 // <body/> element w/ content 71 private static String HTML_BODY_BODY = 72 "<body bgcolor=\"#ffffff\" text=\"#000000\"><a href=\"google.com\">test2</a></body>"; 73 private static String HTML_BODY_RESULT = "<a href=\"google.com\">test2</a>"; 74 // No <body/> tag; just content 75 private static String HTML_NO_BODY_BODY = 76 "<a href=\"google.com\">test3</a>"; 77 private static String HTML_NO_BODY_RESULT = "<a href=\"google.com\">test3</a>"; 78 79 private static String REPLY_INTRO_TEXT = "\n\n" + SENDER + " wrote:\n\n"; 80 private static String REPLY_INTRO_HTML = "<br><br>" + SENDER + " wrote:<br><br>"; 81 private Context mMockContext; 82 private String mForwardIntro; 83 84 public Rfc822OutputTests () { 85 super(EmailProvider.class, EmailContent.AUTHORITY); 86 } 87 88 @Override 89 public void setUp() throws Exception { 90 super.setUp(); 91 mMockContext = getMockContext(); 92 mForwardIntro = mMockContext.getString(R.string.message_compose_fwd_header_fmt, SUBJECT, 93 SENDER, RECIPIENT_TO, RECIPIENT_CC); 94 } 95 96 // TODO Create more tests here. Specifically, we should test to make sure that forward works 97 // properly instead of just reply 98 99 // TODO Write test that ensures that bcc is handled properly (i.e. sent/not send depending 100 // on the flag passed to writeTo 101 102 private Message createTestMessage(String text, boolean save) { 103 Message message = new Message(); 104 message.mText = text; 105 message.mFrom = SENDER; 106 message.mFlags = Message.FLAG_TYPE_REPLY; 107 message.mTextReply = REPLY_TEXT_BODY; 108 message.mHtmlReply = BODY_HTML_REPLY; 109 message.mIntroText = REPLY_INTRO_TEXT; 110 if (save) { 111 message.save(mMockContext); 112 } 113 return message; 114 } 115 116 private Body createTestBody(Message message) { 117 Body body = Body.restoreBodyWithMessageId(mMockContext, message.mId); 118 return body; 119 } 120 121 /** 122 * Test for buildBodyText(). 123 * Compare with expected values. 124 * Also test the situation where the message has no body. 125 */ 126 public void testBuildBodyText() { 127 // Test sending a message *without* using smart reply 128 Message message1 = createTestMessage("", true); 129 Body body1 = createTestBody(message1); 130 String[] bodyParts; 131 132 bodyParts = Rfc822Output.buildBodyText(body1, false); 133 assertEquals(REPLY_INTRO_TEXT + ">" + REPLY_TEXT_BODY, bodyParts[0]); 134 135 message1.mId = -1; // Changing the message; need to reset the id 136 message1.mText = TEXT; 137 message1.save(mMockContext); 138 body1 = createTestBody(message1); 139 140 bodyParts = Rfc822Output.buildBodyText(body1, false); 141 assertEquals(TEXT + REPLY_INTRO_TEXT + ">" + REPLY_TEXT_BODY, bodyParts[0]); 142 143 // We have an HTML reply and no text reply; use the HTML reply 144 message1.mId = -1; // Changing the message; need to reset the id 145 message1.mTextReply = null; 146 message1.save(mMockContext); 147 body1 = createTestBody(message1); 148 149 bodyParts = Rfc822Output.buildBodyText(body1, false); 150 assertEquals(TEXT + REPLY_INTRO_TEXT + BODY_TEXT_REPLY_HTML, bodyParts[0]); 151 152 // We have no HTML or text reply; use nothing 153 message1.mId = -1; // Changing the message; need to reset the id 154 message1.mHtmlReply = null; 155 message1.save(mMockContext); 156 body1 = createTestBody(message1); 157 158 bodyParts = Rfc822Output.buildBodyText(body1, false); 159 assertEquals(TEXT + REPLY_INTRO_TEXT, bodyParts[0]); 160 161 // Test sending a message *with* using smart reply 162 Message message2 = createTestMessage("", true); 163 Body body2 = createTestBody(message2); 164 165 bodyParts = Rfc822Output.buildBodyText(body2, true); 166 assertEquals(REPLY_INTRO_TEXT, bodyParts[0]); 167 168 message2.mId = -1; // Changing the message; need to reset the id 169 message2.mText = TEXT; 170 message2.save(mMockContext); 171 body2 = createTestBody(message2); 172 173 bodyParts = Rfc822Output.buildBodyText(body2, true); 174 assertEquals(TEXT + REPLY_INTRO_TEXT, bodyParts[0]); 175 176 // We have an HTML reply and no text reply; use nothing (smart reply) 177 message2.mId = -1; // Changing the message; need to reset the id 178 message2.mTextReply = null; 179 message2.save(mMockContext); 180 body2 = createTestBody(message2); 181 182 bodyParts = Rfc822Output.buildBodyText(body2, true); 183 assertEquals(TEXT + REPLY_INTRO_TEXT, bodyParts[0]); 184 185 // We have no HTML or text reply; use nothing 186 message2.mId = -1; // Changing the message; need to reset the id 187 message2.mTextReply = null; 188 message2.mHtmlReply = null; 189 message2.save(mMockContext); 190 body2 = createTestBody(message2); 191 192 bodyParts = Rfc822Output.buildBodyText(body2, true); 193 assertEquals(TEXT + REPLY_INTRO_TEXT, bodyParts[0]); 194 } 195 196 /** 197 * Test for buildBodyText(). 198 * Compare with expected values. 199 */ 200 public void testBuildBodyTextWithForward() { 201 Message msg = new Message(); 202 msg.mText = TEXT; 203 msg.mFrom = SENDER; 204 msg.mTo = RECIPIENT_TO; 205 msg.mCc = RECIPIENT_CC; 206 msg.mSubject = SUBJECT; 207 msg.mFlags = Message.FLAG_TYPE_FORWARD; 208 msg.mTextReply = REPLY_TEXT_BODY; 209 msg.mIntroText = mForwardIntro; 210 msg.save(mMockContext); 211 Body body = createTestBody(msg); 212 String[] bodyParts = Rfc822Output.buildBodyText(body, false); 213 assertEquals(TEXT + mForwardIntro + REPLY_TEXT_BODY, bodyParts[0]); 214 } 215 216 public void testWriteToText() throws IOException, MessagingException { 217 // Create a simple text message 218 Message msg = new Message(); 219 msg.mText = TEXT; 220 msg.mFrom = SENDER; 221 // Save this away 222 msg.save(mMockContext); 223 224 // Write out an Rfc822 message 225 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 226 Rfc822Output.writeTo(mMockContext, msg, byteStream, true, false, null); 227 228 // Get the message and create a mime4j message from it 229 // We'll take advantage of its parsing capabilities 230 ByteArrayInputStream messageInputStream = 231 new ByteArrayInputStream(byteStream.toByteArray()); 232 org.apache.james.mime4j.message.Message mimeMessage = 233 new org.apache.james.mime4j.message.Message(messageInputStream); 234 235 // Make sure its structure is correct 236 checkMimeVersion(mimeMessage); 237 assertFalse(mimeMessage.isMultipart()); 238 assertEquals("text/plain", mimeMessage.getMimeType()); 239 } 240 241 @SuppressWarnings("unchecked") 242 public void testWriteToAlternativePart() throws IOException, MessagingException { 243 // Create a message with alternative part 244 Message msg = new Message(); 245 msg.mText = TEXT; 246 msg.mFrom = SENDER; 247 msg.mAttachments = new ArrayList<Attachment>(); 248 // Attach a meeting invitation, which needs to be sent as multipart/alternative 249 Attachment att = new Attachment(); 250 att.mContentBytes = "__CONTENT__".getBytes("UTF-8"); 251 att.mFlags = Attachment.FLAG_ICS_ALTERNATIVE_PART; 252 att.mMimeType = "text/calendar"; 253 att.mFileName = "invite.ics"; 254 msg.mAttachments.add(att); 255 // Save this away 256 msg.save(mMockContext); 257 258 // Write out an Rfc822 message 259 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 260 Rfc822Output.writeTo(mMockContext, msg, byteStream, true, false, null); 261 262 // Get the message and create a mime4j message from it 263 // We'll take advantage of its parsing capabilities 264 ByteArrayInputStream messageInputStream = 265 new ByteArrayInputStream(byteStream.toByteArray()); 266 org.apache.james.mime4j.message.Message mimeMessage = 267 new org.apache.james.mime4j.message.Message(messageInputStream); 268 269 // Make sure its structure is correct 270 checkMimeVersion(mimeMessage); 271 assertTrue(mimeMessage.isMultipart()); 272 Header header = mimeMessage.getHeader(); 273 Field contentType = header.getField("content-type"); 274 assertTrue(contentType.getBody().contains("multipart/alternative")); 275 Multipart multipart = (Multipart)mimeMessage.getBody(); 276 List<BodyPart> partList = multipart.getBodyParts(); 277 assertEquals(2, partList.size()); 278 Entity part = partList.get(0); 279 assertEquals("text/plain", part.getMimeType()); 280 part = partList.get(1); 281 assertEquals("text/calendar", part.getMimeType()); 282 header = part.getHeader(); 283 assertNull(header.getField("content-disposition")); 284 } 285 286 @SuppressWarnings("unchecked") 287 public void testWriteToMixedPart() throws IOException, MessagingException { 288 // Create a message with a mixed part 289 Message msg = new Message(); 290 msg.mText = TEXT; 291 msg.mFrom = SENDER; 292 msg.mAttachments = new ArrayList<Attachment>(); 293 // Attach a simple html "file" 294 Attachment att = new Attachment(); 295 att.mContentBytes = "<html>Hi</html>".getBytes("UTF-8"); 296 att.mMimeType = "text/html"; 297 att.mFileName = "test.html"; 298 msg.mAttachments.add(att); 299 // Save this away 300 msg.save(mMockContext); 301 302 // Write out an Rfc822 message 303 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 304 Rfc822Output.writeTo(mMockContext, msg, byteStream, true, false, null); 305 306 // Get the message and create a mime4j message from it 307 // We'll take advantage of its parsing capabilities 308 ByteArrayInputStream messageInputStream = 309 new ByteArrayInputStream(byteStream.toByteArray()); 310 org.apache.james.mime4j.message.Message mimeMessage = 311 new org.apache.james.mime4j.message.Message(messageInputStream); 312 313 // Make sure its structure is correct 314 checkMimeVersion(mimeMessage); 315 assertTrue(mimeMessage.isMultipart()); 316 Header header = mimeMessage.getHeader(); 317 Field contentType = header.getField("content-type"); 318 assertTrue(contentType.getBody().contains("multipart/mixed")); 319 Multipart multipart = (Multipart)mimeMessage.getBody(); 320 List<BodyPart> partList = multipart.getBodyParts(); 321 assertEquals(2, partList.size()); 322 Entity part = partList.get(0); 323 assertEquals("text/plain", part.getMimeType()); 324 part = partList.get(1); 325 assertEquals("text/html", part.getMimeType()); 326 header = part.getHeader(); 327 assertNotNull(header.getField("content-disposition")); 328 } 329 330 /** 331 * Tests various types of HTML reply text -- with full <html/> tags, 332 * with just the <body/> tags and without any surrounding tags. 333 */ 334 public void testGetHtmlBody() { 335 String actual; 336 actual = Rfc822Output.getHtmlBody(HTML_FULL_BODY); 337 assertEquals(HTML_FULL_RESULT, actual); 338 actual = Rfc822Output.getHtmlBody(HTML_BODY_BODY); 339 assertEquals(HTML_BODY_RESULT, actual); 340 actual = Rfc822Output.getHtmlBody(HTML_NO_BODY_BODY); 341 assertEquals(HTML_NO_BODY_RESULT, actual); 342 } 343 344 /** 345 * Test the boundary digit. We modify it indirectly. 346 */ 347 public void testBoundaryDigit() { 348 // Use getBoundary() to update the boundary digit 349 Rfc822Output.sBoundaryDigit = 0; // ensure it starts at a known value 350 351 Rfc822Output.getNextBoundary(); 352 assertEquals(1, Rfc822Output.sBoundaryDigit); 353 Rfc822Output.getNextBoundary(); 354 assertEquals(2, Rfc822Output.sBoundaryDigit); 355 Rfc822Output.getNextBoundary(); 356 assertEquals(3, Rfc822Output.sBoundaryDigit); 357 Rfc822Output.getNextBoundary(); 358 assertEquals(4, Rfc822Output.sBoundaryDigit); 359 Rfc822Output.getNextBoundary(); 360 assertEquals(5, Rfc822Output.sBoundaryDigit); 361 Rfc822Output.getNextBoundary(); 362 assertEquals(6, Rfc822Output.sBoundaryDigit); 363 Rfc822Output.getNextBoundary(); 364 assertEquals(7, Rfc822Output.sBoundaryDigit); 365 Rfc822Output.getNextBoundary(); 366 assertEquals(8, Rfc822Output.sBoundaryDigit); 367 Rfc822Output.getNextBoundary(); 368 assertEquals(9, Rfc822Output.sBoundaryDigit); 369 Rfc822Output.getNextBoundary(); // roll over 370 assertEquals(0, Rfc822Output.sBoundaryDigit); 371 } 372 373 private final int BOUNDARY_COUNT = 12; 374 public void testGetNextBoundary() { 375 String[] resultArray = new String[BOUNDARY_COUNT]; 376 for (int i = 0; i < BOUNDARY_COUNT; i++) { 377 resultArray[i] = Rfc822Output.getNextBoundary(); 378 } 379 for (int i = 0; i < BOUNDARY_COUNT; i++) { 380 final String result1 = resultArray[i]; 381 for (int j = 0; j < BOUNDARY_COUNT; j++) { 382 if (i == j) { 383 continue; // Don't verify the same result 384 } 385 final String result2 = resultArray[j]; 386 assertFalse(result1.equals(result2)); 387 } 388 } 389 } 390 391 /** 392 * Confirm that the constructed message includes "MIME-VERSION: 1.0" 393 */ 394 private void checkMimeVersion(org.apache.james.mime4j.message.Message mimeMessage) { 395 Header header = mimeMessage.getHeader(); 396 Field contentType = header.getField("MIME-VERSION"); 397 assertTrue(contentType.getBody().equals("1.0")); 398 } 399 } 400