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