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