mitm.common.mail.BodyPartUtils.java Source code

Java tutorial

Introduction

Here is the source code for mitm.common.mail.BodyPartUtils.java

Source

/*
 * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo.
 * 
 * This file is part of Djigzo email encryption.
 *
 * Djigzo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License 
 * version 3, 19 November 2007 as published by the Free Software 
 * Foundation.
 *
 * Djigzo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public 
 * License along with Djigzo. If not, see <http://www.gnu.org/licenses/>
 *
 * Additional permission under GNU AGPL version 3 section 7
 * 
 * If you modify this Program, or any covered work, by linking or 
 * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, 
 * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, 
 * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, 
 * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, 
 * wsdl4j-1.6.1.jar (or modified versions of these libraries), 
 * containing parts covered by the terms of Eclipse Public License, 
 * tyrex license, freemarker license, dom4j license, mx4j license,
 * Spice Software License, Common Development and Distribution License
 * (CDDL), Common Public License (CPL) the licensors of this Program grant 
 * you additional permission to convey the resulting work.
 */
package mitm.common.mail;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Enumeration;

import javax.mail.BodyPart;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimePart;

import mitm.common.mail.matcher.HeaderMatcher;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BodyPartUtils {
    private static Logger logger = LoggerFactory.getLogger(BodyPartUtils.class);

    public static MimeBodyPart makeContentBodyPart(MimePart sourcePart, HeaderMatcher matcher)
            throws MessagingException, IOException {
        MimeBodyPart newBodyPart = new MimeBodyPart();

        newBodyPart.setContent(sourcePart.getContent(), sourcePart.getContentType());

        HeaderUtils.copyHeaders(sourcePart, newBodyPart, matcher);

        return newBodyPart;
    }

    public static MimeBodyPart makeContentBodyPartRaw(MimeBodyPart sourceMessage, HeaderMatcher matcher)
            throws IOException, MessagingException {
        /*
         * getRawInputStream() can throw a MessagingException when the source message is a MimeMessage
         * that is created in code (ie. not from a stream)
         */
        InputStream messageStream = sourceMessage.getRawInputStream();

        MimeBodyPart newBodyPart = new MimeBodyPart(messageStream);

        HeaderUtils.copyHeaders(sourceMessage, newBodyPart, matcher);

        return newBodyPart;
    }

    public static MimeBodyPart makeContentBodyPartRaw(MimeMessage sourceMessage, HeaderMatcher matcher)
            throws IOException, MessagingException {
        /*
         * getRawInputStream() can throw a MessagingException when the source message is a MimeMessage
         * that is created in code (ie. not from a stream)
         */
        InputStream messageStream = sourceMessage.getRawInputStream();

        MimeBodyPart newBodyPart = new MimeBodyPart(messageStream);

        HeaderUtils.copyHeaders(sourceMessage, newBodyPart, matcher);

        return newBodyPart;
    }

    /**
     * This is the only way I know of to create a new MimeBodyPart from another MimeBodyPart which is safe
     * for signed email. All other methods break the signature when quoted printable soft breaks are used.
     * 
     * example of quoted printable soft breaks:
     * 
     * Content-Transfer-Encoding: quoted-printable
        
     * soft break example =
     * another line =
     *
     * All other methods will re-encode and removes the soft breaks.
     * 
     * @param sourceMessage
     * @param matcher
     * @return
     * @throws IOException
     * @throws MessagingException
     */
    @SuppressWarnings("unchecked")
    public static MimeBodyPart makeContentBodyPartRawBytes(MimeBodyPart sourceMessage, HeaderMatcher matcher)
            throws IOException, MessagingException {
        /*
         * getRawInputStream() can throw a MessagingException when the source message is a MimeMessage
         * that is created in code (ie. not from a stream)
         */
        InputStream messageStream = sourceMessage.getRawInputStream();

        byte[] rawMessage = IOUtils.toByteArray(messageStream);

        InternetHeaders destinationHeaders = new InternetHeaders();

        Enumeration<Header> sourceHeaders = sourceMessage.getAllHeaders();

        HeaderUtils.copyHeaders(sourceHeaders, destinationHeaders, matcher);

        MimeBodyPart newBodyPart = new MimeBodyPart(destinationHeaders, rawMessage);

        return newBodyPart;
    }

    /**
     * This is the only way I know of to create a new MimeBodyPart from another Message which is safe
     * for signed email. All other methods break the signature when quoted printable soft breaks are used.
     * 
     * example of quoted printable soft breaks:
     * 
     * Content-Transfer-Encoding: quoted-printable
        
     * soft break example =
     * another line =
     *
     * All other methods will re-encode and removes the soft breaks.
     * 
     * @param sourceMessage
     * @param matcher
     * @return
     * @throws IOException
     * @throws MessagingException
     */
    @SuppressWarnings("unchecked")
    public static MimeBodyPart makeContentBodyPartRawBytes(MimeMessage sourceMessage, HeaderMatcher matcher)
            throws IOException, MessagingException {
        /*
         * getRawInputStream() can throw a MessagingException when the source message is a MimeMessage
         * that is created in code (ie. not from a stream)
         */
        InputStream messageStream = sourceMessage.getRawInputStream();

        byte[] rawMessage = IOUtils.toByteArray(messageStream);

        InternetHeaders destinationHeaders = new InternetHeaders();

        Enumeration<Header> sourceHeaders = sourceMessage.getAllHeaders();

        HeaderUtils.copyHeaders(sourceHeaders, destinationHeaders, matcher);

        MimeBodyPart newBodyPart = new MimeBodyPart(destinationHeaders, rawMessage);

        return newBodyPart;
    }

    public static MimeBodyPart makeContentBodyPart(MimeBodyPart sourcePart, HeaderMatcher matcher)
            throws MessagingException, IOException {
        MimeBodyPart mimeBodyPart = null;

        try {
            mimeBodyPart = makeContentBodyPartRawBytes(sourcePart, matcher);
        } catch (IOException e) {
            logger.error("makeContentBodyPartRaw failed. Trying makeContentBodyPartStandard.", e);
        } catch (MessagingException e) {
            /*
             * makeContentBodyPartRaw is not supported if the sourcePart is a MimeMessage that was
             * created in code ie. not from a stream
             */
            logger.debug("makeContentBodyPartRaw failed. Trying makeContentBodyPartStandard.", e);
        }

        if (mimeBodyPart == null) {
            mimeBodyPart = makeContentBodyPart((MimePart) sourcePart, matcher);
        }

        return mimeBodyPart;
    }

    public static MimeBodyPart makeContentBodyPart(MimeMessage sourcePart, HeaderMatcher matcher)
            throws MessagingException, IOException {
        MimeBodyPart mimeBodyPart = null;

        try {
            mimeBodyPart = makeContentBodyPartRawBytes(sourcePart, matcher);
        } catch (IOException e) {
            logger.error("makeContentBodyPartRaw failed. Trying makeContentBodyPartStandard.", e);
        } catch (MessagingException e) {
            /*
             * makeContentBodyPartRaw is not supported if the sourcePart is a MimeMessage that was
             * created in code ie. not from a stream
             */
            logger.debug("makeContentBodyPartRaw failed. Trying makeContentBodyPartStandard.", e);
        }

        if (mimeBodyPart == null) {
            mimeBodyPart = makeContentBodyPart((MimePart) sourcePart, matcher);
        }

        return mimeBodyPart;
    }

    /**
     * Creates a MimeMessage from a Part. If part is an instance of MimeMessage the part is returned as a new
     * MimeMessage if clone is true. If clone is false the Part is cast to MimeMessage and returned.
     */
    public static MimeMessage toMessage(Part part, boolean clone) throws MessagingException, IOException {
        if (part instanceof MimeMessage) {
            return clone ? MailUtils.cloneMessage((MimeMessage) part) : (MimeMessage) part;
        }

        byte[] bytes = MailUtils.partToByteArray(part);

        MimeMessage message = MailUtils.byteArrayToMessage(bytes);

        return message;
    }

    public static MimeMessage toMessage(Part part) throws MessagingException, IOException {
        return toMessage(part, false /* do not clone */);
    }

    /**
     * Creates a MimeBodyPart from a Part. If part is an instance of MimeBodyPart the part is returned without 
     * any modification.
     */
    public static MimeBodyPart toMimeBodyPart(Part part) throws MessagingException, IOException {
        if (part instanceof MimeBodyPart) {
            return (MimeBodyPart) part;
        }

        byte[] bytes = MailUtils.partToByteArray(part);

        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);

        MimeBodyPart mimeBodyPart = new MimeBodyPart(bis);

        return mimeBodyPart;
    }

    /**
     * Creates a MimeBodyPart with the provided message attached as a RFC822 attachment. 
     */
    public static MimeBodyPart toRFC822(MimeMessage message, String filename) throws MessagingException {
        MimeBodyPart bodyPart = new MimeBodyPart();

        bodyPart.setContent(message, "message/rfc822");
        /* somehow the content-Type header is not set so we need to set it ourselves */
        bodyPart.setHeader("Content-Type", "message/rfc822");
        bodyPart.setDisposition(Part.INLINE);
        bodyPart.setFileName(filename);

        return bodyPart;
    }

    /**
     * Extracts the message from the RFC822 attachment.
     * @throws MessagingException 
     * @throws IOException 
     */
    public static MimeMessage extractFromRFC822(Part rfc822) throws IOException, MessagingException {
        if (!rfc822.isMimeType("message/rfc822")) {
            throw new MessagingException("Part is-not-a message/rfc822 but " + rfc822.getContentType());
        }

        return new MimeMessage(MailSession.getDefaultSession(), rfc822.getInputStream());
    }

    /**
     * Searches for an embedded RFC822 messages. Returns the first embedded RFC822 it finds.
     * Only one level deep is searched.
     *  
     * Returns null if there are no embedded message.
     */
    public static MimeMessage searchForRFC822(MimeMessage message) throws MessagingException, IOException {
        /*
         * Fast fail. Only multipart mixed messages are supported. 
         */
        if (!message.isMimeType("multipart/mixed")) {
            return null;
        }

        Multipart mp;

        try {
            mp = (Multipart) message.getContent();
        } catch (IOException e) {
            throw new MessagingException("Error getting message content.", e);
        }

        MimeMessage embeddedMessage = null;

        for (int i = 0; i < mp.getCount(); i++) {
            BodyPart part = mp.getBodyPart(i);

            if (part.isMimeType("message/rfc822")) {
                embeddedMessage = BodyPartUtils.extractFromRFC822(part);

                break;
            }
        }

        return embeddedMessage;
    }

    /**
     * Returns the plain text body of the message.
     */
    public static String getPlainBody(final MimeMessage message) throws MessagingException, IOException {
        return getPlainBodyAndAttachments(message, null);
    }

    /**
     * Returns the plain text body of the message and it's attachments. The attachments collection is cleared before use.
     */
    public static String getPlainBodyAndAttachments(final MimeMessage message, Collection<Part> attachments)
            throws MessagingException, IOException {
        String body = null;

        for (int maxLevel = 1; maxLevel <= 2; maxLevel++) {
            if (attachments != null) {
                attachments.clear();
            }

            body = getPlainBodyAndAttachments(message, attachments, 0, maxLevel);

            if (body != null) {
                break;
            }
        }

        return body;
    }

    private static String getPlainBodyAndAttachments(final Part part, Collection<Part> attachments, int level,
            int maxLevel) throws MessagingException, IOException {
        String body = null;

        if (part.isMimeType("text/plain")) {
            body = (String) part.getContent();
        } else {
            /*
             * Maximum level deep
             */
            if (level <= maxLevel && part.isMimeType("multipart/*")) {
                Multipart mp = (Multipart) part.getContent();

                int partCount = mp.getCount();

                for (int i = 0; i < partCount; i++) {
                    Part child = mp.getBodyPart(i);

                    if (body == null) {
                        body = getPlainBodyAndAttachments(child, attachments, level + 1, maxLevel);

                        if (body == null && part.isMimeType("multipart/mixed")) {
                            if (attachments != null) {
                                attachments.add(child);
                            }
                        }
                    } else if (part.isMimeType("multipart/mixed")) {
                        if (attachments != null) {
                            attachments.add(child);
                        }
                    }
                }
            }
        }

        return body;
    }
}