mitm.common.mail.MailUtils.java Source code

Java tutorial

Introduction

Here is the source code for mitm.common.mail.MailUtils.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.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;

import javax.activation.DataHandler;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimePart;
import javax.mail.util.ByteArrayDataSource;

import mitm.common.util.FileConstants;
import mitm.common.util.MiscStringUtils;
import mitm.common.util.SizeUtils;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.DeferredFileOutputStream;

public class MailUtils {
    public static final byte[] CRLF_BYTES = new byte[] { '\r', '\n' };

    @SuppressWarnings("rawtypes")
    public static void writeMessageRaw(MimePart part, OutputStream output) throws IOException, MessagingException {
        Enumeration lines = part.getAllHeaderLines();

        /* step through all header lines */
        while (lines.hasMoreElements()) {
            String header = (String) lines.nextElement();

            output.write(MiscStringUtils.toAsciiBytes(header));
            output.write(CRLF_BYTES);
        }

        output.write(CRLF_BYTES);

        InputStream input;

        /*
         * If message is created from a stream Javamail will return the raw stream. If
         * the message is created from 'scratch' getRawInputStream throws a 
         * MessagingException. We will therefore first try the raw version and if
         * that fails we will try getInputStream.
         *
         * Only MimeMessage has getRawInputStream so we check whether the part 
         * is a MimeMessage
         */
        if (part instanceof MimeMessage) {
            try {
                input = ((MimeMessage) part).getRawInputStream();
            } catch (MessagingException e) {
                /*
                 * Could be that we are using Javamail 1.3 which does not support 
                 * getRawInputStream. Use the inputStream instead
                 */
                input = part.getInputStream();
            }
        } else {
            input = part.getInputStream();
        }

        IOUtils.copy(input, output);
    }

    /**
     * Saves the MimePart to the output stream
     * @param part
     * @param output
     * @throws IOException
     * @throws MessagingException
     */
    public static void writeMessage(MimePart part, OutputStream output) throws IOException, MessagingException {
        /*
         * we need to store the result in a temporary buffer so we can fall back 
         * on a different procedure when writeTo fails. If not, MimePart#writeTo might
         * fail halfway and some data has already been written to output. 
         */
        DeferredFileOutputStream buffer = new DeferredFileOutputStream(SizeUtils.MB, FileConstants.TEMP_FILE_PREFIX,
                null, null);

        /*
         * First try the writeTo method. Sometimes this will fail when the message uses 
         * an unsupported or corrupt encoding. For example the content encoding says that
         * the message is base64 encoded but it is not.  
         */
        try {
            part.writeTo(buffer);

            /*
             * Need to close the DeferredFileOutputStream before we can write
             */
            buffer.close();

            /* 
             * writeTo was successful so we can copy the result to the final output
             */
            buffer.writeTo(output);
        } catch (IOException e) {
            writeMessageRaw(part, output);
        } catch (MessagingException e) {
            writeMessageRaw(part, output);
        } finally {
            /*
             * Delete any possible temp file used by DeferredFileOutputStream.
             */
            FileUtils.deleteQuietly(buffer.getFile());
        }
    }

    public static void writeMessage(MimePart part, File outputFile) throws IOException, MessagingException {
        FileOutputStream output = new FileOutputStream(outputFile);

        try {
            writeMessage(part, output);
        } finally {
            output.close();
        }
    }

    /**
     * Loads a message from the given file using the default mail session
     * @param file file containing the RFC822 message source
     * @return the loaded file
     * @throws FileNotFoundException 
     * @throws MessagingException 
     */
    public static MimeMessage loadMessage(File file) throws FileNotFoundException, MessagingException {
        InputStream input = new BufferedInputStream(new FileInputStream(file));

        try {
            return loadMessage(input);
        } finally {
            IOUtils.closeQuietly(input);
        }
    }

    /**
     * Loads a message from the input stream using the default mail session
     * @param input
     * @return
     * @throws MessagingException
     */
    public static MimeMessage loadMessage(InputStream input) throws MessagingException {
        MimeMessage message = new MimeMessage(MailSession.getDefaultSession(), input);

        return message;
    }

    /**
     * Checks whether the message can be converted to RFC2822 raw message source. Messages that
     * contain unsupported encoding types, corrupts content transfer encoding etc. will result
     * in a MessagingException or IOException.
     * @param message
     * @throws MessagingException
     * @throws IOException
     */
    public static void validateMessage(MimeMessage message) throws MessagingException, IOException {
        OutputStream bitsink = new OutputStream() {
            @Override
            public void write(int ignore) {
            }
        };

        message.writeTo(bitsink);
    }

    /**
     * Checks whether the message can be converted to RFC2822 raw message source. Messages that
     * contain unsupported encoding types, corrupts content transfer encoding etc. will result
     * in a MessagingException or IOException.
     * @param message
     * @throws MessagingException
     * @throws IOException
     */
    public static void validateMessage(Part part) throws MessagingException, IOException {
        OutputStream bitsink = new OutputStream() {
            @Override
            public void write(int ignore) {
            }
        };

        part.writeTo(bitsink);
    }

    /**
     * Creates a new MimeMessage which is a duplicate of the source message.
     */
    public static MimeMessage cloneMessage(MimeMessage sourceMessage) throws MessagingException {
        return new MimeMessage(sourceMessage);
    }

    /**
     * Returns the Part as a byte array. The Part is converted to a byte array using MimeMessage.writeTo. 
     * If a message contains a corrupt body (like incorrect base64) writeTo can throw an exception. 
     */
    public static byte[] partToByteArray(Part part) throws IOException, MessagingException {
        /*
         * create a buffer with some 'guess' of the size plus some constant value. If size is unknown size = -1 so
         * the buffer will be 1023.
         */
        ByteArrayOutputStream bos = new ByteArrayOutputStream(part.getSize() + 1024);

        part.writeTo(bos);

        return bos.toByteArray();
    }

    /**
     * Converts the bytes to a MimeMessage
     * @throws MessagingException 
     */
    public static MimeMessage byteArrayToMessage(byte[] bytes) throws MessagingException {
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);

        MimeMessage message = loadMessage(bis);

        return message;
    }

    /**
     * Converts any 8bit encoded parts to 7bit. Returns true if a part (or all parts) were converted from
     * 8bit to 7bit.
     * 
     * @param part
     * @throws MessagingException
     */
    public static boolean convertTo7Bit(MimePart part) throws MessagingException, IOException {
        boolean converted = false;

        if (part.isMimeType("multipart/*")) {
            Multipart parts = (Multipart) part.getContent();

            int count = parts.getCount();

            for (int i = 0; i < count; i++) {
                boolean partConverted = convertTo7Bit((MimePart) parts.getBodyPart(i));

                if (partConverted) {
                    converted = true;
                }
            }
        } else if ("8bit".equalsIgnoreCase(part.getEncoding())) {
            String encoding = part.isMimeType("text/*") ? "quoted-printable" : "base64";

            /*
             * We need to use a ByteArrayDataSource to make sure it will always be encoded
             */
            part.setDataHandler(
                    new DataHandler(new ByteArrayDataSource(part.getInputStream(), part.getContentType())));

            part.setHeader("Content-Transfer-Encoding", encoding);
            part.addHeader("X-MIME-Autoconverted", "from 8bit to " + encoding + " by Djigzo");

            converted = true;
        }

        return converted;
    }
}