1 package test.atlassian.mail;
2
3 import com.atlassian.core.user.UserUtils;
4 import com.atlassian.mail.MailUtils;
5 import com.opensymphony.user.DuplicateEntityException;
6 import com.opensymphony.user.ImmutableException;
7 import com.opensymphony.user.User;
8 import junit.framework.TestCase;
9 import org.apache.commons.io.IOUtils;
10 import test.mock.mail.MockMessage;
11
12 import java.io.ByteArrayInputStream;
13 import java.io.FileNotFoundException;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Properties;
20 import javax.activation.DataHandler;
21 import javax.mail.Address;
22 import javax.mail.BodyPart;
23 import javax.mail.Message;
24 import javax.mail.MessagingException;
25 import javax.mail.Multipart;
26 import javax.mail.Part;
27 import javax.mail.Session;
28 import javax.mail.internet.AddressException;
29 import javax.mail.internet.InternetAddress;
30 import javax.mail.internet.MimeBodyPart;
31 import javax.mail.internet.MimeMessage;
32 import javax.mail.internet.MimeMultipart;
33 import javax.mail.internet.NewsAddress;
34
35 public class TestMailUtils extends TestCase
36 {
37 private User testuser;
38
39 public TestMailUtils(String s)
40 {
41 super(s);
42 }
43
44 protected void setUp() throws Exception
45 {
46 super.setUp();
47 testuser = UserUtils.createUser("test user", "password", "test@atlassian.com", "test user fullname");
48 }
49
50 protected void tearDown() throws Exception
51 {
52 testuser.remove();
53 }
54
55
56 public void testGetAttachmentWithMissingDisposition() throws MessagingException, IOException
57 {
58 MimeMessage msg = new MimeMessage(Session.getDefaultInstance(new Properties()));
59 MimeMultipart content = new MimeMultipart();
60
61 BodyPart text = new MimeBodyPart();
62 text.setText("this is a text");
63 content.addBodyPart(text);
64
65 MimeBodyPart attachmentPart = new MimeBodyPart();
66 attachmentPart.setDataHandler(new DataHandler("testing", "text/plain"));
67 attachmentPart.addHeader("Content-Type", "text/plain;\n" +
68 "\tname=\"text.txt\"");
69 content.addBodyPart(attachmentPart);
70 msg.setContent(content);
71
72 MailUtils.Attachment[] attachments = MailUtils.getAttachments(msg);
73 assertEquals(1, attachments.length);
74 assertEquals("text.txt", attachments[0].getFilename());
75 }
76
77 public void testEmptyBodyPart() throws MessagingException
78 {
79 MimeMessage msg = new MimeMessage(Session.getDefaultInstance(new Properties()));
80
81 MimeMultipart multi = new MimeMultipart();
82 BodyPart plainText = new MimeBodyPart();
83 BodyPart deliveryStatus = new MimeBodyPart();
84
85 plainText.setText("test");
86 deliveryStatus.setHeader("Content-Type","text/plain" );
87
88 multi.addBodyPart(plainText);
89 multi.addBodyPart(deliveryStatus);
90
91 msg.setContent(multi);
92 msg.saveChanges();
93
94 String body = MailUtils.getBody(msg);
95 assertEquals("test", body);
96 }
97
98 public void testParseAddresses() throws AddressException
99 {
100 InternetAddress[] addresses = MailUtils.parseAddresses("edwin@atlassian.com, mike@atlassian.com, owen@atlassian.com");
101
102 assertEquals(new InternetAddress("edwin@atlassian.com"), addresses[0]);
103 assertEquals(new InternetAddress("mike@atlassian.com"), addresses[1]);
104 assertEquals(new InternetAddress("owen@atlassian.com"), addresses[2]);
105 }
106
107 public void testGetBody() throws MessagingException, IOException
108 {
109 testGetBodyWithContentType("text/plain");
110 }
111
112 public void testGetBodyWithDifferentContentTypes() throws MessagingException, IOException
113 {
114 testGetBodyWithContentType("TEXT/PLAIN");
115 testGetBodyWithContentType("tExt/plAIN");
116
117 testGetBodyWithContentType("text/html");
118 testGetBodyWithContentType("text/HTML");
119 testGetBodyWithContentType("TEXT/HTML");
120 }
121
122 private void testGetBodyWithContentType(final String contentType) throws MessagingException
123 {
124 Message msg = new MimeMessage(null, new ByteArrayInputStream("test".getBytes()));
125 msg.setContent("test message", "");
126 msg.setHeader("Content-Type", contentType);
127 assertEquals("test message", MailUtils.getBody(msg));
128
129 msg.setContent(new Integer(1), contentType);
130 assertNull(MailUtils.getBody(msg));
131 }
132
133 public void testGetAuthorFromSender() throws MessagingException, ImmutableException, DuplicateEntityException
134 {
135 Message msg = new MimeMessage(null, new ByteArrayInputStream("test".getBytes()));
136 assertNull(MailUtils.getAuthorFromSender(msg));
137
138 msg.setFrom(new InternetAddress("edwin@atlassian.com"));
139 assertNull(MailUtils.getAuthorFromSender(msg));
140
141 msg.setFrom(new InternetAddress("test@atlassian.com"));
142 assertNotNull(MailUtils.getAuthorFromSender(msg));
143 assertEquals(testuser, MailUtils.getAuthorFromSender(msg));
144 }
145
146 public void testGetFirstValidUser() throws ImmutableException, DuplicateEntityException, AddressException
147 {
148 Address[] blankaddresslist = { };
149 assertNull(MailUtils.getFirstValidUser(blankaddresslist));
150
151 Address[] nouseraddresslist = { new InternetAddress("edwin@atlassian.com"), new InternetAddress("owen@atlassian.coM") };
152 assertNull(MailUtils.getFirstValidUser(nouseraddresslist));
153
154 Address[] addresslist = { new InternetAddress("mike@atlassian.com"), new InternetAddress("test@atlassian.com") };
155 assertNotNull(MailUtils.getFirstValidUser(addresslist));
156 assertEquals(testuser, MailUtils.getFirstValidUser(addresslist));
157 }
158
159
160
161
162
163
164
165
166 public void testGetSenders() throws Exception
167 {
168 Address[] blankaddresslist = { };
169
170 Message msg = createMockMessageWithSenders(blankaddresslist);
171 assertEquals(0, MailUtils.getSenders(msg).size());
172
173
174 Address[] allNulls = { null, null };
175 msg = createMockMessageWithSenders(allNulls);
176 assertEquals(0, MailUtils.getSenders(msg).size());
177
178
179 String goodAddress = "good@address.com";
180 Address[] oneGood = { null, new InternetAddress(goodAddress) };
181 msg = createMockMessageWithSenders(oneGood);
182 List senders = MailUtils.getSenders(msg);
183 assertEquals(1, senders.size());
184 assertEquals(goodAddress, senders.get(0));
185
186
187 Address[] mixedBag = {
188 new InternetAddress(goodAddress),
189 new NewsAddress(),
190 null,
191 new InternetAddress(" " + goodAddress) };
192 msg = createMockMessageWithSenders(mixedBag);
193 senders = MailUtils.getSenders(msg);
194 assertEquals(2, senders.size());
195
196 assertEquals(goodAddress, senders.get(0));
197 assertEquals(goodAddress, senders.get(1));
198 }
199
200 private Message createMockMessageWithSenders(final Address[] addresses) throws MessagingException
201 {
202 Message msg = new MockMessage()
203 {
204
205 public Address[] getFrom() throws MessagingException
206 {
207 return addresses;
208 }
209 };
210 return msg;
211 }
212
213
214 public void testHasRecipient() throws MessagingException
215 {
216 Message msg = new MimeMessage(null, new ByteArrayInputStream("test".getBytes()));
217 assertTrue(!MailUtils.hasRecipient("edwin@atlassian.com", msg));
218 msg.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress("edwin@atlassian.com"));
219 assertTrue(MailUtils.hasRecipient("edwin@atlassian.com", msg));
220 }
221
222 public void testCreateAttachmentMimeBodyPartFileNames() throws MessagingException
223 {
224 MimeBodyPart mbp = MailUtils.createAttachmentMimeBodyPart("C:/test/foo/bar/export.zip");
225 assertEquals("export.zip", mbp.getFileName());
226 }
227
228 public void testCreateAttachmentMimeBodyPartFileNames2() throws MessagingException
229 {
230 MimeBodyPart mbp = MailUtils.createAttachmentMimeBodyPart("C:\\test\\foo\\bar\\export.zip");
231 assertEquals("export.zip", mbp.getFileName());
232 }
233
234 public void testGetBodyText() throws MessagingException
235 {
236 assertEquals("This is a simple test mail, which isn't in HTML and has no attachments.",
237 MailUtils.getBody(makeMessageFromResourceFile("/testmails/simplebody.txt")));
238 }
239
240 public void testGetBodyTextIfContentEncodingUnsupported() throws MessagingException
241 {
242 assertEquals("This is a simple test mail, which isn't in HTML and has no attachments.",
243 MailUtils.getBody(makeMessageFromResourceFile("/testmails/simplebodyunsupportedencoding.txt")));
244 }
245
246 public void testGetBodyHtml() throws MessagingException
247 {
248 assertEquals("This is an HTML test mail\nwith a linebreak.",
249 MailUtils.getBody(makeMessageFromResourceFile("/testmails/simplehtml.txt")));
250 }
251
252 public void testGetBodyMultipartAlternativeGetsTextMatch() throws MessagingException
253 {
254 assertEquals("This is the text part of the mail",
255 MailUtils.getBody(makeMessageFromResourceFile("/testmails/multipart.txt")));
256 }
257
258 public void testGetBodyMultipartGetsFirstTextMatch() throws MessagingException
259 {
260 assertEquals("This is the first part",
261 MailUtils.getBody(makeMessageFromResourceFile("/testmails/multipartbothtext.txt")));
262 }
263
264 public void testGetBodyMultipartGetsHtmlIfNoText() throws MessagingException
265 {
266 assertEquals("This is\nthe HTML part of the mail",
267 MailUtils.getBody(makeMessageFromResourceFile("/testmails/multipartnotext.txt")));
268 }
269
270 public void testGetBodyMultipartNoMatch() throws MessagingException
271 {
272 assertEquals("", MailUtils.getBody(makeMessageFromResourceFile("/testmails/multipartnobody.txt")));
273 }
274
275 public void testGetBodyMultipartMixed() throws MessagingException
276 {
277 assertEquals("Text alternative", MailUtils.getBody(makeMessageFromResourceFile("/testmails/multipartmixedotherorder.txt")));
278 }
279
280 public void testGetBodyMultipartMixedWithLotsOfTextAndHtml() throws MessagingException
281 {
282 assertEquals("Text alternative\nThis is the first part after the alternative\nThis is the second part after the alternative",
283 MailUtils.getBody(makeMessageFromResourceFile("/testmails/multipartmixedtextandhtml.txt")));
284 }
285
286 public void testGetAttachmentsNoAttachments() throws MessagingException, IOException
287 {
288 assertEquals("no attachments", 0,
289 MailUtils.getAttachments(makeMessageFromResourceFile("/testmails/multipart.txt")).length);
290 }
291
292 public void testGetAttachmentsIfContentEncodingIsNotSupported() throws MessagingException, IOException
293 {
294 Message message = makeMessageFromResourceFile("/testmails/multipartunsupportedencoding.txt");
295 assertEquals("This is the text part of the mail", MailUtils.getBody(message));
296 assertEquals("no attachments", 0,
297 MailUtils.getAttachments(message).length);
298 }
299
300 public void testGetAttachmentsHasAttachments() throws MessagingException, IOException
301 {
302 MailUtils.Attachment[] attachments = MailUtils.getAttachments(makeMessageFromResourceFile("/testmails/multipartmixed.txt"));
303 assertEquals("has attachments", 2, attachments.length);
304 assertEquals("Attachment 1", new String(attachments[0].getContents()));
305 assertEquals("image/jpeg", attachments[0].getContentType().substring(0, 10));
306 assertEquals("bugger.jpg", attachments[0].getFilename());
307 assertEquals("html<br>attachment", new String(attachments[1].getContents()));
308 assertEquals("text/html", attachments[1].getContentType().substring(0, 9));
309 assertEquals("foo.html", attachments[1].getFilename());
310 }
311
312 private Message makeMessageFromResourceFile(String resourceName) throws MessagingException
313 {
314 return new MimeMessage(null, this.getClass().getResourceAsStream(resourceName));
315 }
316
317
318 public void testGetContentTypeFromHeaderValue()
319 {
320 final String input = "text/plain";
321 final String actual = MailUtils.getContentType(input);
322 final String expected = input;
323
324 assertEquals(expected, actual);
325 }
326
327 public void testGetContentTypeFromHeaderValueWithParameter()
328 {
329 final String input = "text/plain; something=somevalue";
330 final String actual = MailUtils.getContentType(input);
331 final String expected = "text/plain";
332
333 assertEquals(expected, actual);
334 }
335
336
337
338
339
340 final static boolean PLAIN_TEXT_YES = true;
341 final static boolean PLAIN_TEXT_NO = false;
342
343 final static boolean HTML_YES = true;
344 final static boolean HTML_NO = false;
345
346 final static boolean INLINE_YES = true;
347 final static boolean INLINE_NO = false;
348
349 final static boolean ATTACHMENT_YES = true;
350 final static boolean ATTACHMENT_NO = false;
351
352 public void testIsPlainTextPart() throws Exception
353 {
354 this.checkParts("OutlookPlainText.msg", PLAIN_TEXT_YES, HTML_YES, INLINE_NO, ATTACHMENT_NO);
355 }
356
357 public void testIsHtmlPart() throws Exception
358 {
359 this.checkParts("OutlookHtml.msg", PLAIN_TEXT_YES, HTML_YES, INLINE_NO, ATTACHMENT_NO);
360 }
361
362 public void testIsImageAttachedPart() throws Exception
363 {
364 this.checkParts("OutlookHtmlImageAttached.msg", PLAIN_TEXT_YES, HTML_YES, INLINE_NO, ATTACHMENT_YES);
365 }
366
367 public void testIsInlineImagePart() throws Exception
368 {
369 this.checkParts("OutlookHtmlInlineImage.msg", PLAIN_TEXT_YES, HTML_YES, INLINE_YES, ATTACHMENT_NO);
370 }
371
372 public void testIsInlineImagePartWherePartHasDisposition() throws Exception
373 {
374 this.checkParts("ThunderbirdHtmlAndPlainTextInlineImage.msg", PLAIN_TEXT_YES, HTML_YES, INLINE_YES, ATTACHMENT_NO);
375 }
376
377 public void testIsAttachmentPart() throws Exception
378 {
379 this.checkParts("OutlookHtmlBinaryAttachment.msg", PLAIN_TEXT_YES, HTML_YES, INLINE_NO, ATTACHMENT_YES);
380 }
381
382 public void testIsRelatedPart() throws Exception
383 {
384 assertFalse(MailUtils.isPartRelated(makeMessageFromResourceFile("/testmails/multipartmixedtextandhtml.txt")));
385 assertTrue(MailUtils.isPartRelated(makeMessageFromResourceFile("/testmails/multipartrelated.txt")));
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401 void checkParts(final String filename, final boolean plainTextExpected, final boolean htmlExpected, final boolean inlineExpected, final boolean attachmentExpected)
402 throws Exception
403 {
404 assertNotNull(filename);
405
406 final Part[] parts = this.createPartsFromMessage(filename);
407 assertNotNull("parts", parts);
408 assertTrue("Expected atleast 1 part but got " + parts.length + " part(s)", parts.length > 0);
409
410
411 boolean plainTextFound = false;
412 boolean htmlFound = false;
413 boolean inlineFound = false;
414 boolean attachmentFound = false;
415
416 for (int i = 0; i < parts.length; i++)
417 {
418 final Part part = parts[i];
419
420 if (MailUtils.isPartPlainText(part))
421 {
422 assertTrue("PlainText part found when none was expected", plainTextExpected);
423 plainTextFound = true;
424 continue;
425 }
426
427 if (MailUtils.isPartHtml(part))
428 {
429 assertTrue("Html part found when none was expected", htmlExpected);
430 htmlFound = true;
431 }
432
433 if (MailUtils.isPartInline(part))
434 {
435 assertTrue("Inline part found when none was expected", inlineExpected);
436 inlineFound = true;
437
438 final boolean reportedEmpty = MailUtils.isContentEmpty(part);
439 assertFalse("All inline parts in the prepared msg files are never empty...", reportedEmpty);
440 }
441
442 if (MailUtils.isPartAttachment(part))
443 {
444 assertTrue("Attachment part found when none was expected", attachmentExpected);
445 attachmentFound = true;
446
447 final boolean reportedEmpty = MailUtils.isContentEmpty(part);
448 assertFalse("All attachments parts in the prepared msg files are never empty...", reportedEmpty);
449 }
450 }
451
452 assertEquals("Expected to find a plain text part but one was not found", plainTextExpected, plainTextFound);
453 assertEquals("Expected to find a html part but one was not found", htmlExpected, htmlFound);
454 assertEquals("Expected to find a inline part but one was not found", inlineExpected, inlineFound);
455 assertEquals("Expected to find a attachment part but one was not found", attachmentExpected, attachmentFound);
456 }
457
458
459
460
461
462
463
464
465
466 Part[] createPartsFromMessage(final String filename) throws Exception
467 {
468 assertNotNull(filename);
469
470 InputStream fis = null;
471
472 try
473 {
474 fis = getTestEmailFile(filename);
475
476
477 final Message message = new MimeMessage(Session.getDefaultInstance(new Properties()), fis);
478 final Part[] parts = this.getAllParts(message);
479 assertNotNull("parts", parts);
480 assertTrue("Expected atleast 1 part but got " + parts.length + " part(s)", parts.length > 0);
481
482 return parts;
483 }
484 finally
485 {
486 IOUtils.closeQuietly(fis);
487 }
488 }
489
490 private InputStream getTestEmailFile(String filename) throws FileNotFoundException
491 {
492
493 return getClass().getResourceAsStream("/testmails/" + filename);
494 }
495
496
497
498
499
500
501
502
503
504 Part[] getAllParts(final Message message) throws Exception
505 {
506 final List partsList = new ArrayList();
507 this.processMessageParts(message, partsList);
508 final Part[] parts = (Part[]) partsList.toArray(new Part[0]);
509 return parts;
510 }
511
512 void processMessageParts(final Message message, final Collection parts) throws Exception
513 {
514 final Object content = message.getContent();
515 this.handlePartOrContent(content, parts);
516 }
517
518 void handlePartOrContent(final Object partOrContent, final Collection parts) throws Exception
519 {
520 if (partOrContent instanceof Multipart)
521 {
522 this.processMultipartParts((Multipart) partOrContent, parts);
523 return;
524 }
525
526 if (partOrContent instanceof BodyPart)
527 {
528 final BodyPart bodyPart = (BodyPart) partOrContent;
529 final Object bodyPartContent = bodyPart.getContent();
530
531 if (bodyPartContent instanceof Multipart)
532 {
533 this.handlePartOrContent(bodyPartContent, parts);
534 }
535 else
536 {
537 parts.add(bodyPart);
538 }
539 return;
540 }
541 parts.add((Part) partOrContent);
542 }
543
544 void processMultipartParts(final Multipart multipart, final Collection parts) throws Exception
545 {
546 final int partsCount = multipart.getCount();
547 for (int i = 0; i < partsCount; i++)
548 {
549 final BodyPart bodyPart = multipart.getBodyPart(i);
550 this.handlePartOrContent(bodyPart, parts);
551 }
552 }
553
554
555 static final String X_PKCS7_SIGNATURE = "application/x-pkcs7-signature";
556
557 public void testPositivePartIsSignature() throws Exception
558 {
559 final Message message = createMessageWithAttachment(X_PKCS7_SIGNATURE, "attachment", "filename", "xxxx");
560 final boolean actual = MailUtils.isPartSignaturePKCS7(message);
561 assertTrue("The message being tested is of \"" + X_PKCS7_SIGNATURE + "\" but MailUtils returned false instead of true", actual);
562 }
563
564 public void testNegativePartIsSignature() throws Exception
565 {
566 final Message message = createMessageWithAttachment("text/plain", "", null, "xxxx");
567 final boolean actual = MailUtils.isPartSignaturePKCS7(message);
568 assertFalse("The message being tested is of \"" + X_PKCS7_SIGNATURE + "\" but MailUtils returned true for a text/plain when it should have returned false.", actual);
569 }
570
571 public void testTextPartContentIsEmpty() throws Exception
572 {
573 final Message message = createMessageWithAttachment("text/plain", "", null, "");
574 final boolean actual = MailUtils.isContentEmpty(message);
575 assertTrue("The plaintext message contains a part that should be empty.", actual);
576 }
577
578
579 public void testTextPartContentIsEmptyOnNullContent() throws Exception
580 {
581 final Message message = createMessageWithAttachment("text/plain", "", null, (String) null);
582 final boolean actual = MailUtils.isContentEmpty(message);
583 assertTrue("The plaintext message contains a part that should be empty.", actual);
584 }
585
586 public void testTextPartContentIsEmptyBecauseContainsOnlyWhitespace() throws Exception
587 {
588 final Message message = createMessageWithAttachment("text/plain", "", null, " ");
589 final boolean actual = MailUtils.isContentEmpty(message);
590 assertTrue("The plaintext message contains a part that should be empty.", actual);
591 }
592
593 public void testBinaryAttachmentPartContentIsEmpty() throws Exception
594 {
595 final Message message = createMessageWithAttachment("binary/octet-stream", "file.bin", Part.ATTACHMENT, new byte[0]);
596 final boolean actual = MailUtils.isContentEmpty(message);
597 assertTrue("The attachment part should be empty.", actual);
598 }
599
600 public void testBinaryAttachmentPartContentIsNotEmpty() throws Exception
601 {
602 final Message message = createMessageWithAttachment("binary/octet-stream", "file.bin", Part.ATTACHMENT, " NOT EMPTY!!! ".getBytes());
603 final boolean actual = MailUtils.isContentEmpty(message);
604 assertFalse("The attachment part should be empty.", actual);
605 }
606
607 public void testInlineAttachment() throws Exception
608 {
609 final Message message = createMessageWithAttachment("image/jpeg", "image.jpeg", Part.INLINE, " NOT EMPTY!!! ".getBytes());
610 final boolean actual = MailUtils.isPartInline(message);
611 assertFalse("The attachment part is an inline part.", actual);
612 }
613
614
615
616
617
618
619
620 public void testInlineAttachmentThatIsntBase64Encoded() throws Exception
621 {
622 final Message message = createMessageWithAttachment("image/jpeg", "image.jpeg", null, " NOT EMPTY!!! ".getBytes());
623 message.setHeader("Content-ID", "1234567890");
624 final boolean actual = MailUtils.isPartInline(message);
625 assertFalse("The attachment part isnt not base64 encoded.", actual);
626 }
627
628
629
630
631
632
633
634
635
636
637
638 Message createMessageWithAttachment(final String mimeType, final String disposition, final String filename, final String content)
639 throws Exception
640 {
641 assertNotNull("mimeType", mimeType);
642 assertTrue("mimeType must not be empty", mimeType.length() > 0);
643
644 final Message message = new MimeMessage(Session.getDefaultInstance(new Properties()));
645 message.setContent(content, mimeType);
646 if (null != filename && filename.length() > 0)
647 {
648 message.setFileName(filename);
649 }
650 message.setHeader("Content-Type", mimeType);
651 message.setDisposition(disposition);
652 return message;
653 }
654
655 Message createMessageWithAttachment(final String mimeType, final String disposition, final String filename, final byte[] content)
656 throws Exception
657 {
658 assertNotNull("mimeType", mimeType);
659 assertTrue("mimeType must not be empty", mimeType.length() > 0);
660
661 final Message message = new MimeMessage(Session.getDefaultInstance(new Properties()));
662 message.setContent(new ByteArrayInputStream(content), mimeType);
663 if (null != filename && filename.length() > 0)
664 {
665 message.setFileName(filename);
666 }
667 message.setHeader("Content-Type", mimeType);
668 message.setDisposition(disposition);
669 return message;
670 }
671 }