org.simplejavamail.internal.util.MimeMessageParser.java Source code

Java tutorial

Introduction

Here is the source code for org.simplejavamail.internal.util.MimeMessageParser.java

Source

/*
 * <strong>heavily modified version based on org.apache.commons.mail.util.MimeMessageParser.html</strong>
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.simplejavamail.internal.util;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.util.ByteArrayDataSource;
import java.io.*;
import java.util.*;

/**
 * <strong>heavily modified version based on org.apache.commons.mail.util.MimeMessageParser.html</strong>
 * Parses a MimeMessage and stores the individual parts such a plain text,
 * HTML text and attachments.
 *
 * @version current: MimeMessageParser.java 2016-02-25 Benny Bottema
 */
public class MimeMessageParser {

    private static final List<String> DEFAULT_HEADERS = new ArrayList<>();

    static {
        // taken from: protected javax.mail.internet.InternetHeaders constructor
        /*
         * When extracting information to create an Email, we're not interested in the following headers:
         */
        DEFAULT_HEADERS.add("Return-Path");
        DEFAULT_HEADERS.add("Received");
        DEFAULT_HEADERS.add("Resent-Date");
        DEFAULT_HEADERS.add("Resent-From");
        DEFAULT_HEADERS.add("Resent-Sender");
        DEFAULT_HEADERS.add("Resent-To");
        DEFAULT_HEADERS.add("Resent-Cc");
        DEFAULT_HEADERS.add("Resent-Bcc");
        DEFAULT_HEADERS.add("Resent-Message-Id");
        DEFAULT_HEADERS.add("Date");
        DEFAULT_HEADERS.add("From");
        DEFAULT_HEADERS.add("Sender");
        DEFAULT_HEADERS.add("Reply-To");
        DEFAULT_HEADERS.add("To");
        DEFAULT_HEADERS.add("Cc");
        DEFAULT_HEADERS.add("Bcc");
        DEFAULT_HEADERS.add("Message-Id");
        DEFAULT_HEADERS.add("In-Reply-To");
        DEFAULT_HEADERS.add("References");
        DEFAULT_HEADERS.add("Subject");
        DEFAULT_HEADERS.add("Comments");
        DEFAULT_HEADERS.add("Keywords");
        DEFAULT_HEADERS.add("Errors-To");
        DEFAULT_HEADERS.add("MIME-Version");
        DEFAULT_HEADERS.add("Content-Type");
        DEFAULT_HEADERS.add("Content-Transfer-Encoding");
        DEFAULT_HEADERS.add("Content-MD5");
        DEFAULT_HEADERS.add(":");
        DEFAULT_HEADERS.add("Content-Length");
        DEFAULT_HEADERS.add("Status");
        // extra headers that should be ignored, which may originate from nested attachments
        DEFAULT_HEADERS.add("Content-Disposition");
        DEFAULT_HEADERS.add("size");
        DEFAULT_HEADERS.add("filename");
        DEFAULT_HEADERS.add("Content-ID");
        DEFAULT_HEADERS.add("name");
        DEFAULT_HEADERS.add("From");
    }

    private final Map<String, DataSource> attachmentList = new HashMap<>();

    private final Map<String, DataSource> cidMap = new HashMap<>();

    private final Map<String, Object> headers = new HashMap<>();

    private final MimeMessage mimeMessage;

    private String plainContent;

    private String htmlContent;

    /**
     * Constructs an instance with the MimeMessage to be extracted.
     *
     * @param message the message to parse
     */
    public MimeMessageParser(final MimeMessage message) {
        this.mimeMessage = message;
    }

    /**
     * Does the actual extraction.
     *
     * @return this instance
     */
    public MimeMessageParser parse() throws MessagingException, IOException {
        this.parse(mimeMessage);
        return this;
    }

    /**
     * @return the 'to' recipients of the message
     */
    public List<InternetAddress> getTo() throws MessagingException {
        return getInternetAddresses(this.mimeMessage.getRecipients(Message.RecipientType.TO));
    }

    /**
     * @return the 'cc' recipients of the message
     */
    public List<InternetAddress> getCc() throws MessagingException {
        return getInternetAddresses(this.mimeMessage.getRecipients(Message.RecipientType.CC));
    }

    /**
     * @return the 'bcc' recipients of the message
     */
    public List<InternetAddress> getBcc() throws MessagingException {
        return getInternetAddresses(this.mimeMessage.getRecipients(Message.RecipientType.BCC));
    }

    private static List<InternetAddress> getInternetAddresses(final Address[] recipients) {
        final List<Address> addresses = (recipients != null) ? Arrays.asList(recipients) : new ArrayList<Address>();
        final List<InternetAddress> mailAddresses = new ArrayList<>();
        for (final Address address : addresses) {
            if (address instanceof InternetAddress) {
                mailAddresses.add((InternetAddress) address);
            }
        }
        return mailAddresses;
    }

    /**
     * @return the 'from' field of the message
     */
    public InternetAddress getFrom() throws MessagingException {
        final Address[] addresses = this.mimeMessage.getFrom();
        if (addresses == null || addresses.length == 0) {
            return null;
        }
        return (InternetAddress) addresses[0];
    }

    /**
     * @return the 'replyTo' address of the email
     */
    public InternetAddress getReplyTo() throws MessagingException {
        final Address[] addresses = this.mimeMessage.getReplyTo();
        if (addresses == null || addresses.length == 0) {
            return null;
        }
        return (InternetAddress) addresses[0];
    }

    /**
     * @return the mail subject
     */
    public String getSubject() throws MessagingException {
        return this.mimeMessage.getSubject();
    }

    /**
     * Extracts the content of a MimeMessage recursively.
     *
     * @param part   the current MimePart
     * @throws MessagingException parsing the MimeMessage failed
     * @throws IOException        parsing the MimeMessage failed
     */
    private void parse(final MimePart part) throws MessagingException, IOException {
        extractCustomUserHeaders(part);

        if (isMimeType(part, "text/plain") && plainContent == null
                && !Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
            plainContent = (String) part.getContent();
        } else {
            if (isMimeType(part, "text/html") && htmlContent == null
                    && !Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
                htmlContent = (String) part.getContent();
            } else {
                if (isMimeType(part, "multipart/*")) {
                    final Multipart mp = (Multipart) part.getContent();
                    final int count = mp.getCount();

                    // iterate over all MimeBodyPart
                    for (int i = 0; i < count; i++) {
                        parse((MimeBodyPart) mp.getBodyPart(i));
                    }
                } else {
                    final DataSource ds = createDataSource(part);
                    // If the diposition is not provided, the part should be treat as attachment
                    if (part.getDisposition() == null || Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
                        this.attachmentList.put(part.getContentID(), ds);
                    } else if (Part.INLINE.equalsIgnoreCase(part.getDisposition())) {
                        this.cidMap.put(part.getContentID(), ds);
                    } else {
                        throw new IllegalStateException("invalid attachment type");
                    }
                }
            }
        }
    }

    private void extractCustomUserHeaders(final MimePart part) throws MessagingException {
        final Enumeration e = part.getAllHeaders();
        while (e.hasMoreElements()) {
            final Object headerObj = e.nextElement();
            if (headerObj instanceof Header) {
                final Header header = (Header) headerObj;
                if (isCustomUserHeader(header)) {
                    headers.put(header.getName(), header.getValue());
                }
            }
        }
    }

    private static boolean isCustomUserHeader(final Header header) {
        return !DEFAULT_HEADERS.contains(header.getName());
    }

    /**
     * Checks whether the MimePart contains an object of the given mime type.
     *
     * @param part     the current MimePart
     * @param mimeType the mime type to check
     * @return {@code true} if the MimePart matches the given mime type, {@code false} otherwise
     * @throws MessagingException parsing the MimeMessage failed
     */
    private static boolean isMimeType(final MimePart part, final String mimeType) throws MessagingException {
        // Do not use part.isMimeType(String) as it is broken for MimeBodyPart
        // and does not really check the actual content type.

        try {
            final ContentType ct = new ContentType(part.getDataHandler().getContentType());
            return ct.match(mimeType);
        } catch (final ParseException ex) {
            return part.getContentType().equalsIgnoreCase(mimeType);
        }
    }

    /**
     * Parses the MimePart to create a DataSource.
     *
     * @param part   the current part to be processed
     * @return the DataSource
     * @throws MessagingException creating the DataSource failed
     * @throws IOException        creating the DataSource failed
     */
    private static DataSource createDataSource(final MimePart part) throws MessagingException, IOException {
        final DataHandler dataHandler = part.getDataHandler();
        final DataSource dataSource = dataHandler.getDataSource();
        final String contentType = getBaseMimeType(dataSource.getContentType());
        final byte[] content = MimeMessageParser.getContent(dataSource.getInputStream());
        final ByteArrayDataSource result = new ByteArrayDataSource(content, contentType);
        final String dataSourceName = getDataSourceName(part, dataSource);

        result.setName(dataSourceName);
        return result;
    }

    /**
     * Determines the name of the data source if it is not already set.
     *
     * @param part       the mail part
     * @param dataSource the data source
     * @return the name of the data source or {@code null} if no name can be determined
     * @throws MessagingException           accessing the part failed
     * @throws UnsupportedEncodingException decoding the text failed
     */
    private static String getDataSourceName(final Part part, final DataSource dataSource)
            throws MessagingException, UnsupportedEncodingException {
        String result = dataSource.getName();

        if (result == null || result.length() == 0) {
            result = part.getFileName();
        }

        if (result != null && result.length() > 0) {
            result = MimeUtility.decodeText(result);
        } else {
            result = null;
        }

        return result;
    }

    /**
     * Read the content of the input stream.
     *
     * @param is the input stream to process
     * @return the content of the input stream
     * @throws IOException reading the input stream failed
     */
    private static byte[] getContent(final InputStream is) throws IOException {
        int ch;
        final byte[] result;

        final ByteArrayOutputStream os = new ByteArrayOutputStream();
        final BufferedInputStream isReader = new BufferedInputStream(is);
        final BufferedOutputStream osWriter = new BufferedOutputStream(os);

        while ((ch = isReader.read()) != -1) {
            osWriter.write(ch);
        }

        osWriter.flush();
        result = os.toByteArray();
        osWriter.close();

        return result;
    }

    /**
     * Parses the mimeType.
     *
     * @param fullMimeType the mime type from the mail api
     * @return the real mime type
     */
    private static String getBaseMimeType(final String fullMimeType) {
        final int pos = fullMimeType.indexOf(';');
        if (pos >= 0) {
            return fullMimeType.substring(0, pos);
        }
        return fullMimeType;
    }

    /**
     * @return {@link #cidMap}
     */
    public Map<String, DataSource> getCidMap() {
        return cidMap;
    }

    /**
     * @return {@link #headers}
     */
    public Map<String, Object> getHeaders() {
        return headers;
    }

    /**
     * @return {@link #plainContent}
     */
    public String getPlainContent() {
        return plainContent;
    }

    /**
     * @return {@link #attachmentList}
     */
    public Map<String, DataSource> getAttachmentList() {
        return attachmentList;
    }

    /**
     * @return {@link #htmlContent}
     */
    public String getHtmlContent() {
        return htmlContent;
    }
}