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