mitm.common.mail.EmailAddressUtils.java Source code

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2008-2012, 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

import javax.mail.Address;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import mitm.common.locale.DefaultLocale;
import mitm.common.util.MiscStringUtils;

import org.apache.commons.lang.CharSet;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Some general purpose utilities for email address validation etc.
 * 
 * @author Martijn Brinkers
 *
 */
public class EmailAddressUtils {
    private final static Logger logger = LoggerFactory.getLogger(EmailAddressUtils.class);

    /*
     * If a local part of an email address only contains these characters, the
     * local part of the email address does not have to be quoted.
     */
    private static final CharSet NO_QUOTE_LOCALPART = CharSet.getInstance("a-zA-Z0-9_+.");

    /*
     * Email address to use when an email address is invalid
     */
    public static final String INVALID_EMAIL = "invalid@invalid.tld";

    /**
      * Email address pattern should not be used for the validation of email addresses because they are not
      * in line with the one from Javamail. Only use this pattern for creating visual mark-ups etc.
      */
    public static final String EMAIL_REG_EXPR = "[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})";

    /**
     * The pattern used for matching email addresses (non strict matching!). Should be used for matching email
     * addresses for visual feedback (highlighting of email addresses etc.)
     */
    public static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REG_EXPR);

    /**
     * Removes quotes from an email address. Email addresses can be quoted. This
     * however can be problematic when we need to use the email address as a key.
     * For example "test"@example.com is in principle the same email address as 
     * test@example.com. This function will strip away any quotes from email
     * addresses.
     * 
     * Outlook sometimes will single quote an email address for email adresses
     * in headers.
     */
    public static String stripQuotes(String email) {
        if (email == null) {
            return null;
        }

        String unquotedEmail = email.trim();

        /*
         * First check if the whole email address is single quoted 
         * (Outlook sometimes adds single quotes to headers)
         * example: 'test@example.com'
         */
        unquotedEmail = MiscStringUtils.unquote(unquotedEmail, '\'');
        unquotedEmail = MiscStringUtils.unquote(unquotedEmail, '"');

        /* the local part of an email address can be quoted. */
        int atIndex = unquotedEmail.lastIndexOf('@');

        /* did the email address contain the @ symbol? */
        if (atIndex > -1) {
            String localPart = unquotedEmail.substring(0, atIndex).trim();
            String domainPart = unquotedEmail.substring(atIndex).trim();

            localPart = MiscStringUtils.unquote(localPart, '"');

            unquotedEmail = localPart + domainPart;
        }

        return unquotedEmail;
    }

    /**
     * Checks whether the given email address is a valid email address using strict checks
     * @param email
     * @return email address if email is valid, null if email is invalid
     */
    public static String validate(String email) {
        /* email size must be smaller than max domain + max local */
        if (email != null && email.length() < (255 + 65)) {
            try {
                InternetAddress validated = new InternetAddress(email, true /* strict checking */);

                return validated.getAddress();
            } catch (AddressException ignored) {
                logger.debug("Email address \"{}\" is not a valid email address", email);
            }
        }
        return null;
    }

    /**
     * Returns true if the email is a valid email address.
     */
    public static boolean isValid(String email) {
        return validate(email) != null;
    }

    public static boolean isValid(InternetAddress address) {
        return address != null && validate(address.getAddress()) != null;
    }

    /**
     * Transform the given email address in a standard form so we can
     * use it as a key using the following steps. When we need to use
     * an email address as an identifier we need to transform an email 
     * address to a standard form ie. we need to canonicalize the email 
     * address because the same email address can be specified in 
     * different ways. In principle email addresses are case sensitive 
     * however in practice email addresses are almost always not case
     * sensitive. We therefore will transform the email address to 
     * lowercase. The local part of an email address can be quoted 
     * to support characters that are normally not allowed for the
     * local part. Examples: "has space"@example.com, "end."@example.com. 
     * These email addresses would, in principle, not be valid 
     * without the quotes. But, "test"@example.com is equal to 
     * test@example.com so we must transform these email addresses
     * to a standard form.
     * 1. remove start/end quotes
     * 2. trim
     * 3. make all characters lower case
     * @param email
     * @return
     */
    public static String canonicalize(String email) {
        if (email == null) {
            return null;
        }

        String canonical = email.toLowerCase(DefaultLocale.getDefaultLocale());

        canonical = stripQuotes(canonical);

        return canonical;
    }

    /**
     * Canonicalizes and validates the given email address. If nullIfInvalid is true and email address is not a valid email address
     * null is returned. If nullIfInvalid is false and email is invalid INVALID_EMAIL is returned.
     */
    public static String canonicalizeAndValidate(String email, boolean nullIfInvalid) {
        String normalized = canonicalize(email);

        normalized = validate(normalized);

        if (normalized == null && !nullIfInvalid) {
            return INVALID_EMAIL;
        }

        return normalized;
    }

    /**
     * Canonicalizes and validates the given collection of email addresses and returns a Set 
     * of email addresses (duplicates will therefore be removed) 
     * @return canonicalized and valid emails. Never null.
     */
    public static Set<String> canonicalizeAndValidate(Collection<String> emails, boolean skipIfInvalid) {
        Set<String> normalizedEmails = new HashSet<String>();

        if (emails != null) {
            for (String email : emails) {
                String normalized = canonicalizeAndValidate(email, skipIfInvalid);

                if (normalized != null) {
                    normalizedEmails.add(normalized);
                }
            }
        }

        return normalizedEmails;
    }

    /**
     * Canonicalizes and validates the given collection of email addresses and returns a Set 
     * of email addresses (duplicates will therefore be removed) 
     * @return canonicalized and valid emails. Never null.
     */
    public static Set<String> canonicalizeAndValidate(String[] emails, boolean skipIfInvalid) {
        return emails != null ? canonicalizeAndValidate(Arrays.asList(emails), skipIfInvalid)
                : canonicalizeAndValidate((Collection<String>) null, skipIfInvalid);
    }

    /**
     * Returns the domain part ie. everything after the @. If there is no @ null is returned. The email address is 
     * not normalized or validated. It's up to the caller to validate the email address.
     */
    public static String getDomain(String email) {
        String domain = StringUtils.substringAfterLast(email, "@");

        if (StringUtils.isEmpty(domain)) {
            domain = null;
        }

        return domain;
    }

    /**
     * See: getDomain(email)
     */
    public static String getDomain(InternetAddress email) {
        if (email == null) {
            return null;
        }

        return getDomain(email.getAddress());
    }

    /**
     * Returns the local part of an email address ie. everything before the @. If there is no @ the complete string is 
     * returned. The email address is not normalized or validated. It's up to the caller to validate the email address.
     * If there is no local part null is returned.
     */
    public static String getLocalPart(String email) {
        String local = StringUtils.substringBeforeLast(email, "@");

        if (StringUtils.isEmpty(local)) {
            local = null;
        }

        return local;
    }

    /**
     * See getLocalPart(String email)
     */
    public static String getLocalPart(InternetAddress email) {
        if (email == null) {
            return null;
        }

        return getLocalPart(email.getAddress());
    }

    /**
     * If the local part contains any characters which are not allowed in the localpart
     * and the localpart is not yet quoted, the local part will be quoted.
     */
    public static String quoteLocalPart(String email) {
        if (email == null) {
            return null;
        }

        String localPart = getLocalPart(email);

        if (localPart == null) {
            return email;
        }

        if (!MiscStringUtils.isQuoted(localPart, '"')) {
            boolean shouldQuote = false;

            for (int i = 0; i < localPart.length(); i++) {
                char c = localPart.charAt(i);

                if (NO_QUOTE_LOCALPART.contains(c)) {
                    continue;
                }

                shouldQuote = true;

                break;
            }

            if (shouldQuote) {
                email = "\"" + localPart + "\"@" + getDomain(email);
            }
        }

        return email;
    }

    /**
     * Returns the email part if address is an InternetAddress, if not toString() is called in the Address.
     */
    public static String getEmailAddress(Address address) {
        String email;

        if (address == null) {
            return null;
        }

        if (address instanceof InternetAddress) {
            email = ((InternetAddress) address).getAddress();
        } else {
            email = address.toString();
        }

        return email;
    }

    /**
     * Returns the first address from addresses. Null if addresses is null or addresses.length == 0.
     */
    public static Address getAddress(Address[] addresses) {
        if (addresses == null || addresses.length == 0) {
            return null;
        }

        return addresses[0];
    }

    /**
     * Returns the Address's as string array. If decode is true, the address will be mime decoded. 
     * Returns null if addresses is null.  Decode is used to decode the user part (if encoded) of an 
     * email address because this is not decoded by default.
     * 
     * WARNING: The returned string includes the user part!
     */
    public static String[] addressesToStrings(Address[] addresses, boolean decode) {
        if (addresses == null) {
            return null;
        }

        return addressesToStrings(Arrays.asList(addresses), decode).toArray(new String[] {});
    }

    /**
     * Returns the Address's as List of strings. If decode is true, the address will be mime decoded. 
     * Returns null if addresses is null. Decode is used to decode the user part (if encoded) of an 
     * email address because this is not decoded by default.
     * 
     * WARNING: The returned string includes the user part!
     */
    public static ArrayList<String> addressesToStrings(Collection<? extends Address> addresses, boolean decode) {
        if (addresses == null) {
            return null;
        }

        ArrayList<String> result = new ArrayList<String>(addresses.size());

        for (Address address : addresses) {
            if (address != null) {
                result.add(decode ? HeaderUtils.decodeTextQuietly(address.toString()) : address.toString());
            }
        }

        return result;
    }

    /**
     * Returns true of search is contained in emails. The emails are canonicalized and checked
     * for validity before being compared.
     */
    public static boolean containsEmail(String search, Collection<String> emails) {
        String filteredSearch = canonicalizeAndValidate(search, true);

        if (filteredSearch == null || emails == null) {
            return false;
        }

        Set<String> filteredEmails = canonicalizeAndValidate(emails, true);

        return filteredEmails.contains(filteredSearch);
    }

    /**
     * Returns true if email is equal to INVALID_EMAIL (invalid@invalid.tld)
     */
    public static boolean isInvalidDummyAddress(String email) {
        return INVALID_EMAIL.equals(email);
    }

    /**
     * Returns true if email is equal to INVALID_EMAIL (invalid@invalid.tld)
     */
    public static boolean isInvalidDummyAddress(InternetAddress email) {
        if (email == null) {
            return false;
        }

        return INVALID_EMAIL.equals(email.getAddress());
    }

    /**
     * Returns the Reply-To not throwing any exception. If the Reply-To header cannot be retrieved, null will be
     * returned.
     */
    public static Address[] getReplyToQuietly(MimeMessage message) {
        Address[] replyTos = null;

        if (message != null) {
            try {
                replyTos = message.getReplyTo();
            } catch (Exception e) {
                logger.debug("Reply-To is not valid", e);
            }
        }

        return replyTos;
    }

    /**
     * Returns the From not throwing any exception. If the From header cannot be retrieved, null will be
     * returned.
     */
    public static Address[] getFromQuietly(MimeMessage message) {
        Address[] froms = null;

        if (message != null) {
            try {
                froms = message.getFrom();
            } catch (Exception e) {
                logger.debug("From is not valid", e);
            }
        }

        return froms;
    }
}