Retrieving email attachment filename with mime4j

2.6k Views Asked by At

I'm trying to use mime4j to parse emails, all is working fine, however I'm not able to get the file name of the attachment. Unfortunately the BodyDescriptor doesn't include this information in the content disposition, or content type fields.

I have read that the MaximalBodyDescriptor will include the filename, however I don't know how to tell the parser to return a MaximalBodyDescriptor object.

My handler is implementing the ContentHandler interface. I can't see an alternate interface which would work.

Any advice appreciated.

2

There are 2 best solutions below

0
On

I recommand you to use Token streams.

It is quite straight forward with it.

You can locate attachment by using headers of your multipart section :

  Content-Disposition:attachment; filename="toto.txt"

You must be carefull when parsing a header with it ... It can be mail headers or multipart section header....

0
On

Here is a helper class that we use successfully to parse e-mails with their attachments. In this approach attach.getFileName() has the filename.

package com.bitplan.smartCRM;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.dom.Body;
import org.apache.james.mime4j.dom.Entity;
import org.apache.james.mime4j.dom.Message;
import org.apache.james.mime4j.dom.MessageBuilder;
import org.apache.james.mime4j.dom.MessageServiceFactory;
import org.apache.james.mime4j.dom.Multipart;
import org.apache.james.mime4j.dom.SingleBody;
import org.apache.james.mime4j.dom.TextBody;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.message.MessageImpl;
import org.apache.james.mime4j.stream.Field;

import com.bitplan.restinterface.Configuration;

/**
 * EMail Helper class
 * 
 * @author wf
 * @author Denis Lunev <[email protected]>
 * @see http 
 *      ://www.mozgoweb.com/posts/how-to-parse-mime-message-using-mime4j-library
 *      /
 */
public class EMailHelper implements ClipboardOwner {

    public static boolean debug = true;
    public static Logger LOGGER = Logger
            .getLogger("com.bitplan.common.EMailHelper");
    private StringBuffer txtBody;
    private StringBuffer htmlBody;
    private ArrayList<Entity> attachments;

    /**
     * get a String from an input Stream
     * 
     * @param inputStream
     * @return
     * @throws IOException
     */
    public String fromInputStream(InputStream inputStream) throws IOException {
        String result = IOUtils.toString(inputStream);
        // System.out.println(result);
        return result;
    }

    /**
     * get the full Mail from a message
     * 
     * @param message
     * @return
     * @throws MessagingException
     * @throws IOException
     */
    public String fullMail(javax.mail.Message message) throws MessagingException,
            IOException {
        StringBuffer sBuf = new StringBuffer();
        @SuppressWarnings("unchecked")
        Enumeration<javax.mail.Header> headers = message.getAllHeaders();
        while (headers.hasMoreElements()) {
            javax.mail.Header header = headers.nextElement();
            sBuf.append(header.getName() + ": " + header.getValue() + "\n");
        }
        sBuf.append(fromInputStream(message.getInputStream()));
        return sBuf.toString();
    }

    /**
     * Authentication
     */
    public static class Authentication {
        enum AuthenticationType {
            pop3, smtp
        };

        String host;
        String user;
        String password;
        AuthenticationType authenticationType;
        Transport mTransport;

        /**
         * create an Authentication from the configuration
         * 
         * @param configuration
         * @param pAuthType
         */
        public Authentication(Configuration configuration,
                AuthenticationType pAuthType) {
            authenticationType = pAuthType;
            String prefix = pAuthType.name() + ".";
            // use prefix e.g. pop3.host / smtp.host
            host = (String) configuration.toMap().get(prefix + "host");
            user = (String) configuration.toMap().get(prefix + "user");
            password = (String) configuration.toMap().get(prefix + "password");
        }

        /**
         * authenticate for sending / receiving e-mail
         * 
         * @throws MessagingException
         */
        public Transport authenticate() throws MessagingException {
            Properties lProps = new Properties();
            Session session = Session.getDefaultInstance(lProps);
            switch (authenticationType) {
            case pop3:
                Store store = session.getStore("pop3");
                store.connect(host, user, password);
                store.close();
                return null;
            case smtp:
                // http://javamail.kenai.com/nonav/javadocs/com/sun/mail/smtp/package-summary.html
                mTransport = session.getTransport("smtp");
                mTransport.connect(host, user, password);
                return mTransport;
            }
            return null;
        }
    }

    /**
     * send the given e-mail
     * 
     * @param email
     * @throws MessagingException
     */
    public void send(EMail email, Configuration configuration)
            throws MessagingException {
        Authentication lAuth = new Authentication(configuration,
                Authentication.AuthenticationType.pop3);
        Properties lProps = System.getProperties();
        lProps.put("mail.smtp.host", lAuth.host);
        // WF 2004-09-18: make sure full qualified domain name is used for localhost
        // the default InetAddress.getLocalHost().getHostName() might not work ...
        // lProps.put("mail.smtp.localhost",java.net.InetAddress.getLocalHost().getCanonicalHostName());
        Session lSession = Session.getInstance(lProps);
        MimeMessage lMsg = new MimeMessage(lSession);
        lMsg.setFrom(new InternetAddress(email.getFromAdr()));
        lMsg.setRecipients(javax.mail.Message.RecipientType.TO,
                InternetAddress.parse(email.getToAdr()));
        if (email.getCC() != null)
            lMsg.setRecipients(javax.mail.Message.RecipientType.CC,
                    InternetAddress.parse(email.getCC()));
        /*
         * if (bcc()!=null) lMsg.setRecipients(Message.RecipientType.BCC,
         * InternetAddress.parse(bcc())); lMsg.setHeader("X-Mailer", "JavaMail");
         * lMsg.setSentDate(new Date()); lMsg.setSubject(subject());
         * lMsg.setText(content()); lMsg.saveChanges(); Transport
         * lTransport=lAuth.authenticate(); if (lTransport!=null)
         * lTransport.sendMessage(lMsg,lMsg.getAllRecipients()); } else {
         * Transport.send(lMsg); }
         */
    }

    /**
     * retrieve pop3 mail from the given host
     * 
     * @param pop3host
     * @param user
     * @param password
     * @throws Exception
     */
    public List<EMail> retrievePop3Mail(EMailManager eMailmanager,
            Configuration configuration) throws Exception {
        List<EMail> result = new ArrayList<EMail>();
        Properties lProps = new Properties();
        Session session = Session.getDefaultInstance(lProps);
        Store store = session.getStore("pop3");
        File attachmentDirectory = (File) configuration.toMap().get(
                "attachmentDirectory");
        // get a pop3 authentication
        Authentication auth = new Authentication(configuration,
                Authentication.AuthenticationType.pop3);
        store.connect(auth.host, auth.user, auth.password);

        Folder remoteInbox = store.getFolder("INBOX");
        remoteInbox.open(Folder.READ_WRITE);
        javax.mail.Message message[] = remoteInbox.getMessages();
        if (message.length > 0) {
            // get all messages
            LOGGER.log(Level.INFO, "Getting " + message.length
                    + " messages from POP3 Server '" + store.getURLName() + "'");
            for (int i = 0; i < message.length; i++) {
                if (!message[i].isSet(Flags.Flag.DELETED)) {
                    EMail email = eMailmanager.create();
                    String mailInput = this.fullMail(message[i]);
                    // System.out.print(mailInput);
                    ByteArrayInputStream mailStream = new ByteArrayInputStream(
                            mailInput.getBytes());
                    this.parseMessage(email, mailStream, attachmentDirectory);
                    result.add(email);
                    message[i].setFlag(Flags.Flag.DELETED, true);
                }
            } // for
        } // if
        remoteInbox.close(true);
        store.close();
        return result;
    }

    /**
     * parse the Message into the given EMail
     * 
     * @param email
     * @param fileName
     * @param attachmentDirectory
     * 
     * @throws Exception
     */
    public void parseMessage(EMail email, String fileName,
            String attachmentDirectory) throws Exception {
        parseMessage(email, new File(fileName), new File(attachmentDirectory));
    }

    /**
     * strip the brackets
     * 
     * @param addressList
     * @return
     */
    public String stripBrackets(MailboxList addressList) {
        String result = null;
        if (addressList != null) {
            result = addressList.toString();
            if (result.startsWith("[") && result.endsWith("]")) {
                result = result.substring(1, result.length() - 1);
            }
        }
        return result;
    }

    /**
     * parse the Message from the given file into the given e-mail using the given
     * attachmentDirectory
     * 
     * @param email
     * @param file
     * @param attachmentDirectory
     * @throws Exception
     */
    public void parseMessage(EMail email, File file, File attachmentDirectory)
            throws Exception {
        if (!file.canRead() || (!file.isFile()))
            throw new IllegalArgumentException(file.getCanonicalPath()
                    + " is not a readable file");
        // Get stream from file
        FileInputStream fis = new FileInputStream(file);
        parseMessage(email, fis, attachmentDirectory);
    }

    /**
     * parse the Message from the given file into the given e-mail using the given
     * attachmentDirectory
     * 
     * @param email
     * @param emailInputStream
     * @param attachmentDirectory
     * @throws Exception
     */
    public void parseMessage(EMail email, InputStream eMailInputStream,
            File attachmentDirectory) throws Exception {

        Message mimeMsg = null;
        if (!attachmentDirectory.isDirectory())
            throw new IllegalArgumentException(attachmentDirectory.getCanonicalPath()
                    + " is not a directory");

        txtBody = new StringBuffer();
        htmlBody = new StringBuffer();
        attachments = new ArrayList<Entity>();
        Exception ex = null;
        try {
            // Create message with stream from file
            // If you want to parse String, you can use:
            // Message mimeMsg = new Message(new
            // ByteArrayInputStream(mimeSource.getBytes()));
            MessageServiceFactory factory = MessageServiceFactory.newInstance();
            MessageBuilder msgBuilder = factory.newMessageBuilder();
            try {
                mimeMsg = msgBuilder.parseMessage(eMailInputStream);
            } catch (Throwable th) {
                LOGGER.log(Level.SEVERE,th.getClass().getName());
                LOGGER.log(Level.SEVERE,th.getMessage());
            }
            if (mimeMsg == null) {
                LOGGER.log(Level.SEVERE, "could not read mime msg:\n",
                        this.fromInputStream(eMailInputStream));
                return;
            }
            // Get some standard headers
            if (mimeMsg.getTo() != null)
                email.setToAdr(stripBrackets(mimeMsg.getTo().flatten()));
            email.setFromAdr(stripBrackets(mimeMsg.getFrom()));
            email.setSubject(mimeMsg.getSubject());
            email.setSendDate(mimeMsg.getDate());
            email.setEMailId(mimeMsg.getMessageId());
            LOGGER.log(Level.INFO, "To: " + email.getToAdr());
            LOGGER.log(Level.INFO, "From: " + email.getFromAdr());
            LOGGER.log(Level.INFO, "Subject: " + mimeMsg.getSubject());

            // Get custom header by name
            Field priorityFld = mimeMsg.getHeader().getField("X-Priority");
            // If header doesn't found it returns null
            if (priorityFld != null) {
                // Print header value
                LOGGER.log(Level.FINEST, "Priority: " + priorityFld.getBody());
            }

            // If message contains many parts - parse all parts
            if (mimeMsg.isMultipart()) {
                Multipart multipart = (Multipart) mimeMsg.getBody();
                parseBodyParts(multipart);
                // fix mime4j 0.7.2 behaviour to have no separate text/plain part
                if (txtBody.length() == 0) {
                    txtBody.append(multipart.getPreamble());
                }
            } else {
                // If it's single part message, just get text body
                String text = getTxtPart(mimeMsg);
                txtBody.append(text);
            }
            email.setContent(txtBody.toString());

            // Print text and HTML bodies
            if (debug) {
                LOGGER.log(Level.FINEST, "Text body: " + txtBody.toString());
                LOGGER.log(Level.FINEST, "Html body: " + htmlBody.toString());
            }
            // loop over attachments
            for (Entity attach : attachments) {
                writeAttachment(attach, attachmentDirectory);
            }
        } catch (Exception cex) {
            ex = cex;
        } finally {
            if (eMailInputStream != null) {
                try {
                    eMailInputStream.close();
                } catch (IOException ioex2) {
                    ioex2.printStackTrace();
                }
            }
        }
        if (ex != null) {
            throw ex;
        }
    }

    /**
     * write the given Attachment
     * 
     * @param attach
     * @param attachmentDirectory
     * @throws IOException
     */
    public void writeAttachment(Entity attach, File attachmentDirectory)
            throws IOException {
        String attName = attach.getFilename();
        // Create file with specified name
        if (attName == null) {
            LOGGER.log(Level.WARNING, "attachment has no file name using 'attachment"
                    + attach.hashCode() + "' instead");
            attName = "attachment" + attach.hashCode();
        }

        FileOutputStream fos = new FileOutputStream(new File(attachmentDirectory,
                attName));
        try {
            writeBody(fos, attach.getBody());
        } finally {
            fos.close();
        }
    }

    /**
     * write the given body to the given fileoutput stream
     * 
     * @param fos
     * @param body
     * @throws IOException
     */
    public void writeBody(FileOutputStream fos, Body body) throws IOException {
        if (body instanceof SingleBody) {
            ((SingleBody) body).writeTo(fos);
        } else if (body instanceof MessageImpl) {
            writeBody(fos, ((MessageImpl) body).getBody());
        } else {
            LOGGER.log(Level.WARNING, "can't handle body of type "
                    + body.getClass().getSimpleName());
        }
    }

    /**
     * This method classifies bodyPart as text, html or attached file
     * 
     * @param multipart
     * @throws IOException
     */
    private void parseBodyParts(Multipart multipart) throws IOException {
        // loop over the parts
        for (Entity part : multipart.getBodyParts()) {
            String mimeType = part.getMimeType();
            if (mimeType.equals("text/plain")) {
                String txt = getTxtPart(part);
                txtBody.append(txt);
            } else if (mimeType.equals("text/html")) {
                String html = getTxtPart(part);
                htmlBody.append(html);
            } else if (part.getDispositionType() != null
                    && !part.getDispositionType().equals("")) {
                // If DispositionType is null or empty, it means that it's multipart,
                // not attached file
                attachments.add(part);
            }

            // If current part contains other, parse it again by recursion
            if (part.isMultipart()) {
                parseBodyParts((Multipart) part.getBody());
            }
        }
    }

    /**
     * 
     * @param part
     * @return
     * @throws IOException
     */
    private String getTxtPart(Entity part) throws IOException {
        // Get content from body
        TextBody tb = (TextBody) part.getBody();
        return this.fromInputStream(tb.getInputStream());
    }

    /**
     * Place a String on the clipboard, and make this class the owner of the
     * Clipboard's contents.
     * 
     * @param aString
     */
    public void setClipboardContents(String aString) {
        StringSelection stringSelection = new StringSelection(aString);
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(stringSelection, this);
    }

    /**
     * get text from the clipboard
     * 
     * @return
     * @throws Exception
     */
    public String getClipboardText() throws Exception {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        String text = (String) clipboard.getData(DataFlavor.stringFlavor);
        return text;
    }

    /**
     * get Mail from clipboard
     * 
     * @param email
     * @param attachmentDirectory
     * @return
     * @throws Exception
     */
    public boolean getMailFromClipboard(EMail email, File attachmentDirectory)
            throws Exception {
        String mailText = getClipboardText();
        if (mailText == null)
            return false;
        this.parseMessage(email,
                new ByteArrayInputStream(mailText.getBytes("UTF-8")),
                attachmentDirectory);
        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * java.awt.datatransfer.ClipboardOwner#lostOwnership(java.awt.datatransfer
     * .Clipboard, java.awt.datatransfer.Transferable)
     */
    @Override
    public void lostOwnership(Clipboard clipboard, Transferable contents) {
    }

}