Clover Coverage Report - Atlassian Mail
Coverage timestamp: Mon Sep 29 2008 21:26:36 CDT
243   861   105   6.23
102   557   0.43   19.5
39     2.69  
2    
 
 
  MailUtils       Line # 55 237 101 82.6% 0.8262032
  MailUtils.Attachment       Line # 85 6 4 100% 1.0
 
  (46)
 
1    /**
2    * Created by IntelliJ IDEA.
3    * User: Edwin
4    * Date: Dec 5, 2002
5    * Time: 2:56:58 PM
6    * To change this template use Options | File Templates.
7    */
8    package com.atlassian.mail;
9   
10    import com.atlassian.core.user.UserUtils;
11    import com.opensymphony.user.EntityNotFoundException;
12    import com.opensymphony.user.User;
13    import org.apache.commons.io.IOUtils;
14    import org.apache.commons.lang.StringUtils;
15    import org.apache.commons.lang.Validate;
16    import org.apache.log4j.Logger;
17   
18    import java.io.BufferedReader;
19    import java.io.ByteArrayOutputStream;
20    import java.io.File;
21    import java.io.FileInputStream;
22    import java.io.FileNotFoundException;
23    import java.io.FileOutputStream;
24    import java.io.IOException;
25    import java.io.InputStream;
26    import java.io.InputStreamReader;
27    import java.io.Reader;
28    import java.io.StringWriter;
29    import java.io.UnsupportedEncodingException;
30    import java.util.ArrayList;
31    import java.util.List;
32    import java.util.Locale;
33    import java.util.StringTokenizer;
34    import java.util.zip.ZipEntry;
35    import java.util.zip.ZipOutputStream;
36    import javax.activation.DataHandler;
37    import javax.activation.DataSource;
38    import javax.activation.FileDataSource;
39    import javax.mail.Address;
40    import javax.mail.BodyPart;
41    import javax.mail.Message;
42    import javax.mail.MessagingException;
43    import javax.mail.Multipart;
44    import javax.mail.Part;
45    import javax.mail.internet.AddressException;
46    import javax.mail.internet.InternetAddress;
47    import javax.mail.internet.MimeBodyPart;
48    import javax.mail.internet.MimeUtility;
49   
50    // TODO: Doesn't handle charsets/encoding very well. Or, indeed, at all.
51    /**
52    * This class contains a bunch of static helper methods that make life a bit easier particularly with the processing of
53    * Parts.
54    */
 
55    public class MailUtils
56    {
57    private static final String DEFAULT_ENCODING = "ISO-8859-1";
58   
59    static final int BUFFER_SIZE = 64 * 1024;
60    static final String MULTIPART_ALTERNATE_CONTENT_TYPE = "multipart/alternative";
61    static final String MULTIPART_RELATED_CONTENT_TYPE = "multipart/related";
62    static final String TEXT_CONTENT_TYPE = "text/plain";
63    static final String MESSAGE_CONTENT_TYPE = "message/rfc822";
64    static final String HTML_CONTENT_TYPE = "text/html";
65    static final String CONTENT_TYPE_X_PKCS7 = "application/x-pkcs7-signature";
66    static final String CONTENT_TYPE_PKCS7 = "application/pkcs7-signature";
67   
68    private static final HtmlToTextConverter htmlConverter = new HtmlToTextConverter();
69    private static final Logger log = Logger.getLogger(MailUtils.class);
70   
71    /**
72    * The content transfer encoding header, which is used to identify whether a part is base64 encoded.
73    */
74    private static final String CONTENT_TRANSFER_ENCODING_HEADER = "Content-Transfer-Encoding";
75   
76    /**
77    * Content header id
78    */
79    private static final String CONTENT_ID_HEADER = "Content-ID";
80   
81    /**
82    * Very simple representation of a mail attachment after it has been
83    * extracted from a message.
84    */
 
85    public static class Attachment {
86    private final String contentType;
87    private final String fileName;
88    private final byte[] contents;
89   
 
90  2 toggle public Attachment(String contentType, String fileName, byte[] contents)
91    {
92  2 this.contentType = contentType;
93  2 this.fileName = fileName;
94  2 this.contents = contents;
95    }
96   
 
97  2 toggle public String getContentType()
98    {
99  2 return contentType;
100    }
101   
 
102  2 toggle public byte[] getContents()
103    {
104  2 return contents;
105    }
106   
 
107  2 toggle public String getFilename()
108    {
109  2 return fileName;
110    }
111    }
112   
113    /**
114    * Parse addresses from a comma (and space) separated string into the proper array
115    */
 
116  10 toggle public static InternetAddress[] parseAddresses(String addresses) throws AddressException
117    {
118  10 List list = new ArrayList();
119  10 list.clear();
120  10 StringTokenizer st = new StringTokenizer(addresses, ", ");
121  22 while (st.hasMoreTokens())
122    {
123  12 list.add(new InternetAddress(st.nextToken()));
124    }
125  10 return (InternetAddress[]) list.toArray(new InternetAddress[list.size()]);
126    }
127   
128    /**
129    * Get the body of the message as a String. The algorithm for finding the body is as follows:
130    *
131    * <ol><li>If the message is a single part, and that part is text/plain, return it.
132    * <li>If the message is a single part, and that part is text/html, convert it to
133    * text (stripping out the HTML) and return it.
134    * <li>If the message is multi-part, return the first text/plain part that isn't marked
135    * explicitly as an attachment.
136    * <li>If the message is multi-part, but does not contain any text/plain parts, return
137    * the first text/html part that isn't marked explicitly as an attachment, converting
138    * it to text and stripping the HTML.
139    * <li>If nothing is found in any of the steps above, return null.
140    * </ol>
141    *
142    * <p>Note: If the message contains nested multipart parts, an HTML part nested at a higher level will
143    * take precedence over a text part nested deeper.
144    *
145    * @param message The message to retrieve the body from
146    * @return The message body, or null if the message could not be parsed
147    * @throws javax.mail.MessagingException If there was an error getting the content from the message
148    */
 
149  12 toggle public static String getBody(Message message) throws MessagingException
150    {
151  12 try
152    {
153  12 String content = extractTextFromPart(message);
154   
155  11 if (content == null)
156    {
157  7 if (message.getContent() instanceof Multipart)
158    {
159  7 content = getBodyFromMultipart((Multipart) message.getContent());
160    }
161    }
162   
163  11 if (content == null)
164    {
165    //didn't match anything above
166  0 log.info("Could not find any body to extract from the message");
167    }
168   
169  11 return content;
170    }
171    catch (ClassCastException cce)
172    {
173  1 log.info("Exception getting the content type of message - probably not of type 'String': " + cce.getMessage());
174  1 return null;
175    }
176    catch (IOException e)
177    {
178  0 log.info("IOException whilst getting message content " + e.getMessage());
179  0 return null;
180    }
181    }
182   
183    /**
184    * Gets all parts of a message that are attachments rather than alternative inline bits.
185    *
186    * @param message the message from which to extract the attachments
187    * @return an array of the extracted attachments
188    */
 
189  3 toggle public static Attachment[] getAttachments(Message message) throws MessagingException, IOException
190    {
191  3 List attachments = new ArrayList();
192   
193  3 if (message.getContent() instanceof Multipart)
194    {
195  3 addAttachments(attachments, (Multipart)message.getContent());
196    }
197   
198  3 return (Attachment[]) attachments.toArray(new Attachment[attachments.size()]);
199    }
200   
 
201  4 toggle private static void addAttachments(List attachments, Multipart parts) throws MessagingException, IOException
202    {
203  13 for (int i = 0, n = parts.getCount(); i < n; i++)
204    {
205  9 BodyPart part = parts.getBodyPart(i);
206   
207  9 if (isAttachment(part))
208    {
209  2 InputStream content = part.getInputStream();
210  2 String contentType = part.getContentType();
211   
212  2 attachments.add(new Attachment(contentType, part.getFileName(), toByteArray(content)));
213    }
214    else
215    {
216  7 try
217    {
218  6 if (part.getContent() instanceof Multipart)
219    {
220  1 addAttachments(attachments, (Multipart) part.getContent());
221    }
222    }
223    catch (UnsupportedEncodingException e)
224    {
225    // ignore because it's probably not a multipart part anyway
226    // if the encoding is unsupported
227  1 log.warn("Unsupported encoding found for part while trying to discover attachments. "
228    + "Attachment will be ignored.", e);
229    }
230    }
231    }
232    }
233   
 
234  9 toggle private static boolean isAttachment(BodyPart part)
235    throws MessagingException
236    {
237  9 return Part.ATTACHMENT.equals(part.getDisposition()) || (Part.INLINE.equals(part.getDisposition()));
238    }
239   
240    /**
241    * Convert the contents of an input stream into a byte array.
242    *
243    * @param in
244    * @return the contents of that stream as a byte array.
245    */
 
246  2 toggle private static byte[] toByteArray(InputStream in) throws IOException
247    {
248  2 ByteArrayOutputStream out = new ByteArrayOutputStream();
249  2 byte[] buf = new byte[512];
250  2 int count;
251  ? while ((count = in.read(buf)) != -1)
252    {
253  2 out.write(buf, 0, count);
254    }
255   
256  2 out.close();
257  2 return out.toByteArray();
258    }
259   
260    /**
261    * Get the user that has the same email address as the author of the message. If multiple
262    * authors, take the first one.
263    *
264    * @param message The message to get the author from.
265    * @return The user who has the same email address as the author of the message
266    * @throws javax.mail.MessagingException If an error occurred getting the message author
267    * @deprecated Now incorporated into JIRA due to app-specific logic, will
268    * be removed in a future release. (complain to chris@atlassian.com)
269    */
 
270  4 toggle public static User getAuthorFromSender(Message message) throws MessagingException
271    {
272  4 return getFirstValidUser(message.getFrom());
273    }
274   
275    /**
276    * Given an array of addresses, this method returns the first valid address.
277    *
278    * @param addresses addresses to be used to search for a User.
279    * @return a User for the email address or null.
280    * @deprecated Now incorporated into JIRA due to app-specific logic, will
281    * be removed in a future release. (complain to chris@atlassian.com)
282    */
 
283  8 toggle public static User getFirstValidUser(Address[] addresses)
284    {
285  8 if (addresses == null || addresses.length == 0)
286  2 return null;
287   
288  11 for (int i = 0; i < addresses.length; i++)
289    {
290  9 if (addresses[i] instanceof InternetAddress)
291    {
292  9 InternetAddress email = (InternetAddress) addresses[i];
293   
294  9 try
295    {
296  9 User validUser = UserUtils.getUserByEmail(email.getAddress());
297  4 return validUser;
298    }
299    catch (EntityNotFoundException e)
300    {
301    // keep cycling
302    }
303    }
304    }
305   
306  2 return null;
307    }
308   
309    /**
310    * @return true if at least one of the recipients matches the email address given.
311    */
 
312  2 toggle public static boolean hasRecipient(String matchEmail, Message message) throws MessagingException
313    {
314  2 Address[] addresses = message.getAllRecipients();
315   
316  2 if (addresses == null || addresses.length == 0)
317  1 return false;
318   
319  1 for (int i = 0; i < addresses.length; i++)
320    {
321  1 InternetAddress email = (InternetAddress) addresses[i];
322   
323  1 if (matchEmail.compareToIgnoreCase(email.getAddress()) == 0)
324  1 return true;
325    }
326   
327  0 return false;
328    }
329   
330    /**
331    * Returns a List<String> of trimmed non-null email addresses from the
332    * given potentially dirty pile of addresses listed as senders on the
333    * given message.
334    * @param message the message from which to get senders.
335    * @return a nice List<String> of email addresses.
336    * @throws MessagingException if the senders can't be retrieved from message.
337    */
 
338  4 toggle public static List /*<String>*/ getSenders(Message message) throws MessagingException
339    {
340   
341  4 ArrayList senders = new ArrayList();
342  4 Address[] addresses = message.getFrom();
343  4 if (addresses != null)
344    {
345  12 for (int i = 0; i < addresses.length; i++)
346    {
347  8 if (addresses[i] instanceof InternetAddress)
348    {
349  3 InternetAddress addr = (InternetAddress) addresses[i];
350    // Trim down the email address to remove any whitespace etc.
351  3 String emailAddress = StringUtils.trimToNull(addr.getAddress());
352  3 if (emailAddress != null)
353    {
354  3 senders.add(emailAddress);
355    }
356    }
357    }
358    }
359  4 return senders;
360    }
361   
362    /**
363    * Produces a mimebodypart object from an attachment file path. An attachment needs to be in this form to be attached
364    * to an email for sending
365    *
366    * @param path
367    * @return
368    * @throws MessagingException
369    */
 
370  4 toggle public static MimeBodyPart createAttachmentMimeBodyPart(String path) throws MessagingException
371    {
372  4 MimeBodyPart attachmentPart = new MimeBodyPart();
373  4 DataSource source = new FileDataSource(path);
374  4 attachmentPart.setDataHandler(new DataHandler(source));
375   
376  4 String fileName = extractFilenameFromPath(path);
377   
378  4 attachmentPart.setFileName(fileName);
379  4 return attachmentPart;
380    }
381   
 
382  4 toggle private static String extractFilenameFromPath(String path) {
383  0 if (path == null) return null;
384  4 StringTokenizer st = new StringTokenizer(path, "\\/");
385   
386  4 String fileName;
387  4 do
388    {
389  14 fileName = st.nextToken();
390    }
391  14 while (st.hasMoreTokens());
392  4 return fileName;
393    }
394   
 
395  0 toggle public static MimeBodyPart createZippedAttachmentMimeBodyPart(String path) throws MessagingException
396    {
397  0 File tmpFile = null;
398  0 String fileName = extractFilenameFromPath(path);
399   
400  0 try {
401  0 tmpFile = File.createTempFile("atlassian", null);
402  0 FileOutputStream fout = new FileOutputStream(tmpFile);
403  0 ZipOutputStream zout = new ZipOutputStream(fout);
404  0 zout.putNextEntry(new ZipEntry(fileName));
405   
406  0 InputStream in = new FileInputStream(path);
407  0 final byte[] buffer = new byte[ BUFFER_SIZE ];
408  0 int n = 0;
409  0 while ( -1 != (n = in.read(buffer)) ) {
410  0 zout.write(buffer, 0, n);
411    }
412  0 zout.close();
413  0 in.close();
414  0 log.debug("Wrote temporary zip of attachment to " + tmpFile);
415    } catch (FileNotFoundException e) {
416  0 String err = "Couldn't find file '"+path+"' on server: "+e;
417  0 log.error(err, e);
418  0 MimeBodyPart mimeBodyPart = new MimeBodyPart();
419  0 mimeBodyPart.setText(err);
420  0 return mimeBodyPart;
421    } catch (IOException e) {
422  0 String err = "Error zipping log file '"+path+"' on server: "+e;
423  0 log.error(err, e);
424  0 MimeBodyPart mimeBodyPart = new MimeBodyPart();
425  0 mimeBodyPart.setText(err);
426  0 return mimeBodyPart;
427    }
428  0 MimeBodyPart attachmentPart = new MimeBodyPart();
429  0 DataSource source = new FileDataSource(tmpFile);
430  0 attachmentPart.setDataHandler(new DataHandler(source));
431  0 attachmentPart.setFileName(fileName+".zip");
432  0 attachmentPart.setHeader("Content-Type", "application/zip");
433  0 return attachmentPart;
434    }
435   
 
436  7 toggle private static String getBodyFromMultipart(Multipart multipart) throws MessagingException, IOException
437    {
438  7 StringBuffer sb = new StringBuffer();
439  7 getBodyFromMultipart(multipart, sb);
440  7 return sb.toString();
441    }
442   
 
443  9 toggle private static void getBodyFromMultipart(Multipart multipart, StringBuffer sb) throws MessagingException, IOException
444    {
445  9 String multipartType = multipart.getContentType();
446   
447    // if an multipart/alternative type we just get the first text or html content found
448  9 if(multipartType != null && compareContentType(multipartType, MULTIPART_ALTERNATE_CONTENT_TYPE))
449    {
450  7 BodyPart part = getFirstInlinePartWithMimeType(multipart, TEXT_CONTENT_TYPE);
451  7 if(part != null)
452    {
453  5 appendMultipartText(extractTextFromPart(part), sb);
454    }
455    else
456    {
457  2 part = getFirstInlinePartWithMimeType(multipart, HTML_CONTENT_TYPE);
458  2 appendMultipartText(extractTextFromPart(part), sb);
459    }
460  7 return;
461    }
462   
463    // otherwise assume multipart/mixed type and construct the contents by retrieving all text and html
464  8 for (int i = 0, n = multipart.getCount(); i < n; i++)
465    {
466  6 BodyPart part = multipart.getBodyPart(i);
467  6 String contentType = part.getContentType();
468   
469  6 if (!Part.ATTACHMENT.equals(part.getDisposition()) && contentType != null)
470    {
471  4 String content = extractTextFromPart(part);
472  4 if (content != null)
473    {
474  2 appendMultipartText(content, sb);
475    }
476  2 else if(part.getContent() instanceof Multipart)
477    {
478  2 getBodyFromMultipart((Multipart) part.getContent(), sb);
479    }
480    }
481    }
482    }
483   
 
484  9 toggle private static void appendMultipartText(String content, StringBuffer sb) throws IOException, MessagingException
485    {
486  9 if (content != null)
487    {
488  8 if(sb.length() > 0) sb.append("\n");
489  8 sb.append(content);
490    }
491    }
492   
 
493  23 toggle private static String extractTextFromPart(Part part) throws IOException, MessagingException,
494    UnsupportedEncodingException
495    {
496  23 if (part == null)
497  1 return null;
498   
499  22 String content = null;
500   
501  22 if (isPartPlainText(part))
502    {
503  10 try
504    {
505  10 content = (String) part.getContent();
506    }
507    catch (UnsupportedEncodingException e)
508    {
509    // If the encoding is unsupported read the content with default encoding
510  2 log.warn("Found unsupported encoding '" + e.getMessage() + "'. Reading content with "
511    + DEFAULT_ENCODING + " encoding.");
512  2 content = getBody(part, DEFAULT_ENCODING);
513    }
514    }
515  12 else if (isPartHtml(part))
516    {
517  3 content = htmlConverter.convert((String) part.getContent());
518    }
519   
520  21 if (content == null)
521    {
522  9 log.warn("Unable to extract text from MIME part with Content-Type '" + part.getContentType());
523    }
524   
525  21 return content;
526    }
527   
 
528  2 toggle private static String getBody(Part part, String charsetName) throws UnsupportedEncodingException,
529    IOException, MessagingException
530    {
531  2 Reader input = null;
532  2 StringWriter output = null;
533  2 try
534    {
535  2 input = new BufferedReader(new InputStreamReader(part.getInputStream(), charsetName));
536  2 output = new StringWriter();
537  2 IOUtils.copy(input, output);
538  2 return output.getBuffer().toString();
539    }
540    finally
541    {
542  2 IOUtils.closeQuietly(input);
543  2 IOUtils.closeQuietly(output);
544    }
545    }
546   
 
547  9 toggle private static BodyPart getFirstInlinePartWithMimeType(Multipart multipart, String mimeType) throws MessagingException
548    {
549  20 for (int i = 0, n = multipart.getCount(); i < n; i++)
550    {
551  17 BodyPart part = multipart.getBodyPart(i);
552  17 String contentType = part.getContentType();
553  17 if (!Part.ATTACHMENT.equals(part.getDisposition()) && contentType != null && compareContentType(contentType, mimeType))
554    {
555  6 return part;
556    }
557    }
558  3 return null;
559    }
560   
 
561  25 toggle private static boolean compareContentType(String contentType, String mimeType)
562    {
563  25 return contentType.toLowerCase().startsWith(mimeType);
564    }
565   
566   
567    /**
568    * Tests if a particular part content type is text/html.
569    *
570    * @param part The part being tested.
571    * @return true if the part content type is text/html
572    * @throws MessagingException if javamail complains
573    */
 
574  22 toggle static public boolean isPartHtml(final Part part) throws MessagingException
575    {
576  22 return HTML_CONTENT_TYPE.equals(MailUtils.getContentType(part));
577    }
578   
579    /**
580    * Tests if the provided part content type is text/plain.
581    *
582    * @param part The part being tested.
583    * @return true if the part content type is text/plain
584    * @throws MessagingException if javamail complains
585    */
 
586  38 toggle static public boolean isPartPlainText(final Part part) throws MessagingException
587    {
588  38 return TEXT_CONTENT_TYPE.equals(MailUtils.getContentType(part));
589    }
590   
591    /**
592    * Tests if the provided part's content type is message/rfc822
593    *
594    * @param part The part being tested.
595    * @return true if the part content type is message/rfc822
596    * @throws MessagingException if javamail complains
597    */
 
598  0 toggle static public boolean isPartMessageType(final Part part) throws MessagingException
599    {
600    // currently, only "message/rfc822" content type is supported
601  0 return MESSAGE_CONTENT_TYPE.equals(getContentType(part));
602    }
603   
604    /**
605    * Tests if the provided part's content type is multipart/related
606    *
607    * @param part The part being tested.
608    * @return true if the part content type is multipart/related
609    * @throws MessagingException if javamail complains
610    */
 
611  2 toggle static public boolean isPartRelated(final Part part) throws MessagingException
612    {
613  2 return MULTIPART_RELATED_CONTENT_TYPE.equals(getContentType(part));
614    }
615   
616    /**
617    * Helper which returns the pure mime/subMime content type less any other extra parameters which may
618    * accompany the header value.
619    *
620    * @param part the mail part to extract the content-type from.
621    * @return the pure mime/subMime type
622    * @throws MessagingException if retrieving the part's Content-Type header fails
623    */
 
624  64 toggle static public String getContentType(final Part part) throws MessagingException
625    {
626  64 checkPartNotNull(part);
627   
628  64 final String contentType = part.getContentType();
629  64 return getContentType(contentType);
630    }
631   
632    /**
633    * Helper which extracts the content type from a header value removing parameters and so on.
634    *
635    * @param headerValue The header value.
636    * @return The actual content type
637    */
 
638  66 toggle static public String getContentType(final String headerValue)
639    {
640  66 checkHeaderValue(headerValue);
641   
642  66 String out = headerValue;
643   
644  66 final int semiColon = headerValue.indexOf(';');
645  66 if (-1 != semiColon)
646    {
647  59 out = headerValue.substring(0, semiColon);
648    }
649   
650  66 return out.trim();
651    }
652   
 
653  66 toggle static private void checkHeaderValue(final String headerValue)
654    {
655  66 Validate.notEmpty(headerValue);
656    }
657   
658    /**
659    * Tests if the content of the part content is empty. The definition of empty depends on whether the content is text
660    * or binary.
661    * <p/>
662    * Text content for content types like plain/text and html/text is defined as being empty if it contains an empty string
663    * after doing a trim(). If the string contains 50 spaces it is still empty whilst a string with a solitary "a"
664    * isnt.
665    * <p/>
666    * For binary content (like images) if the content contains 0 bytes it is empty whilst anything with 1 or more bytes
667    * is NOT considered empty.
668    *
669    * @param part a mail part - may or may not have content.
670    * @return true/false if the content is deemed empty as per above rules.
671    * @throws MessagingException if retrieving content fails.
672    * @throws IOException if retrieving content fails or reading content input stream fails.
673    */
 
674  9 toggle static public boolean isContentEmpty(final Part part) throws MessagingException, IOException
675    {
676  9 checkPartNotNull(part);
677   
678  9 boolean definitelyEmpty = false;
679  9 final Object content = part.getContent();
680  9 if (null == content)
681    {
682  1 definitelyEmpty = true;
683    }
684    else
685    {
686  8 if (content instanceof String)
687    {
688  2 final String stringContent = (String) content;
689  2 definitelyEmpty = StringUtils.isBlank(stringContent);
690    }
691   
692  8 if (content instanceof InputStream)
693    {
694  6 final InputStream inputStream = (InputStream) content;
695  6 try
696    {
697   
698    // try and read a byte.. it we get one its not empty, if we dont its empty.
699  6 final int firstByte = inputStream.read();
700  6 definitelyEmpty = -1 == firstByte;
701   
702    }
703    finally
704    {
705  6 IOUtils.closeQuietly(inputStream);
706    }
707    }
708    }
709   
710  9 return definitelyEmpty;
711    }
712   
713    /**
714    * Asserts that the part parameter is not null, throwing a NullPointerException if the part parameter is null.
715    *
716    * @param part The parameter part
717    */
 
718  97 toggle static private void checkPartNotNull(final Part part)
719    {
720  97 Validate.notNull(part, "part should not be null.");
721    }
722   
723    /**
724    * This method uses a number of checks to determine if the given part actually represents an inline (typically image) part.
725    * Some email clients (aka lotus notes) dont appear to correctly set the disposition to inline so a number of
726    * additional checks are required, hence the multi staged approached.
727    * <p/>
728    * eg. inline images from notes wont have a inline disposition but will have a content id and will also have their
729    * content base64 encoded. This approach helps us correctly identify inline images or other binary parts.
730    *
731    * @param part The part being tested.
732    * @return True if the part is inline false in all other cases.
733    * @throws MessagingException as thrown by java mail
734    */
 
735  12 toggle static public boolean isPartInline(final Part part) throws MessagingException
736    {
737  12 checkPartNotNull(part);
738   
739  12 boolean inline = false;
740   
741    // an inline part is only considered inline if its also got a filename...
742  12 final String disposition = part.getDisposition();
743  12 if (Part.INLINE.equalsIgnoreCase(disposition))
744    {
745  1 final String file = part.getFileName();
746  1 if(!StringUtils.isBlank(file))
747    {
748  1 inline = true;
749    }
750  1 return inline;
751    }
752   
753  11 final boolean gotContentId = MailUtils.hasContentId(part);
754  11 if (!gotContentId)
755    {
756  9 return false;
757    }
758  2 final boolean gotBase64 = MailUtils.isContentBase64Encoded(part);
759  2 if (!gotBase64)
760    {
761  1 return false;
762    }
763   
764  1 return true;
765    }
766   
 
767  11 toggle static private boolean hasContentId(final Part part) throws MessagingException
768    {
769  11 boolean gotContentId = false;
770  11 final String[] contentIds = part.getHeader(MailUtils.CONTENT_ID_HEADER);
771  11 if (null != contentIds)
772    {
773  2 for (int i = 0; i < contentIds.length; i++)
774    {
775  2 final String contentId = contentIds[i];
776  2 if (contentId != null && contentId.length() > 0)
777    {
778  2 gotContentId = true;
779  2 break;
780    }
781    } // for
782    }
783  11 return gotContentId;
784    }
785   
786    /**
787    * Checks if a part's content is base64 encoded by scanning for a content transfer encoding header value.
788    *
789    * @param part THe part being tested.
790    * @return True if the content is base 64 encoded, false in all other cases.
791    * @throws MessagingException if javamail complains
792    */
 
793  2 toggle static private boolean isContentBase64Encoded(final Part part) throws MessagingException
794    {
795  2 boolean gotBase64 = false;
796  2 final String[] contentTransferEncodings = part.getHeader(CONTENT_TRANSFER_ENCODING_HEADER);
797  2 if (null != contentTransferEncodings)
798    {
799  1 for (int i = 0; i < contentTransferEncodings.length; i++)
800    {
801  1 final String contentTransferEncoding = contentTransferEncodings[i];
802  1 if ("base64".equals(contentTransferEncoding))
803    {
804  1 gotBase64 = true;
805  1 break;
806    }
807    }
808    }
809   
810  2 return gotBase64;
811    }
812   
813    /**
814    * Tests if the provided part is an attachment. Note this method does not test if the content is empty etc it merely
815    * tests whether or not the part is an attachment of some sort.
816    *
817    * @param part The part being tested.
818    * @throws MessagingException if javamail complains
819    * @returns True if the part is an attachment otherwise returns false
820    */
 
821  10 toggle static public boolean isPartAttachment(final Part part) throws MessagingException
822    {
823  10 checkPartNotNull(part);
824  10 return Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition());
825    }
826   
827    /**
828    * This method may be used to fix any mime encoded filenames that have been returned by javamail.
829    * No harm can occur from calling this method unnecessarily except for wasting a few cpu cycles...
830    * <p/>
831    * Very probably a MIME-encoded filename - see http://java.sun.com/products/javamail/FAQ.html#encodefilename
832    *
833    * @param filename
834    * @return The fixed filename.
835    * @throws IOException {@see MimeUtility#decodeText}
836    */
 
837  0 toggle static public String fixMimeEncodedFilename(final String filename) throws IOException
838    {
839  0 String newFilename = filename;
840  0 if (filename.startsWith("=?") || filename.endsWith("?="))
841    {
842  0 newFilename = MimeUtility.decodeText(filename);
843    }
844  0 return newFilename;
845    }
846   
847   
848    /**
849    * Tests if a part is actually a signature. This is required to fix JRA-9933.
850    *
851    * @param part a mail part. The part is assumed to have a content-type header.
852    * @return true if the content-type header matches the standard PKCS7 mime types
853    * @throws MessagingException if retrieving the Content-Type from the part fails.
854    */
 
855  2 toggle static public boolean isPartSignaturePKCS7(final Part part) throws MessagingException
856    {
857  2 MailUtils.checkPartNotNull(part);
858  2 final String contentType = MailUtils.getContentType(part).toLowerCase(Locale.getDefault());
859  2 return contentType.startsWith(CONTENT_TYPE_PKCS7) || contentType.startsWith(CONTENT_TYPE_X_PKCS7);
860    }
861    }