273 lines
8.8 KiB
Java
273 lines
8.8 KiB
Java
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed 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 com.android.messaging.util;
|
|
|
|
import com.google.common.base.CharMatcher;
|
|
|
|
/**
|
|
* Parsing the email address
|
|
*/
|
|
public final class EmailAddress {
|
|
private static final CharMatcher ANY_WHITESPACE = CharMatcher.anyOf(
|
|
" \t\n\r\f\u000B\u0085\u2028\u2029\u200D\uFFEF\uFFFD\uFFFE\uFFFF");
|
|
private static final CharMatcher EMAIL_ALLOWED_CHARS = CharMatcher.inRange((char) 0, (char) 31)
|
|
.or(CharMatcher.is((char) 127))
|
|
.or(CharMatcher.anyOf(" @,:<>"))
|
|
.negate();
|
|
|
|
/**
|
|
* Helper method that checks whether the input text is valid email address.
|
|
* TODO: This creates a new EmailAddress object each time
|
|
* Need to make it more lightweight by pulling out the validation code into a static method.
|
|
*/
|
|
public static boolean isValidEmail(final String emailText) {
|
|
return new EmailAddress(emailText).isValid();
|
|
}
|
|
|
|
/**
|
|
* Parses the specified email address. Internationalized addresses are treated as invalid.
|
|
*
|
|
* @param emailString A string representing just an email address. It should
|
|
* not contain any other tokens. <code>"Name<foo@example.org>"</code> won't be valid.
|
|
*/
|
|
public EmailAddress(final String emailString) {
|
|
this(emailString, false);
|
|
}
|
|
|
|
/**
|
|
* Parses the specified email address.
|
|
*
|
|
* @param emailString A string representing just an email address. It should
|
|
* not contain any other tokens. <code>"Name<foo@example.org>"</code> won't be valid.
|
|
* @param i18n Accept an internationalized address if it is true.
|
|
*/
|
|
public EmailAddress(final String emailString, final boolean i18n) {
|
|
allowI18n = i18n;
|
|
valid = parseEmail(emailString);
|
|
}
|
|
|
|
/**
|
|
* Parses the specified email address. Internationalized addresses are treated as invalid.
|
|
*
|
|
* @param user A string representing the username in the email prior to the '@' symbol
|
|
* @param host A string representing the host following the '@' symbol
|
|
*/
|
|
public EmailAddress(final String user, final String host) {
|
|
this(user, host, false);
|
|
}
|
|
|
|
/**
|
|
* Parses the specified email address.
|
|
*
|
|
* @param user A string representing the username in the email prior to the '@' symbol
|
|
* @param host A string representing the host following the '@' symbol
|
|
* @param i18n Accept an internationalized address if it is true.
|
|
*/
|
|
public EmailAddress(final String user, final String host, final boolean i18n) {
|
|
allowI18n = i18n;
|
|
this.user = user;
|
|
setHost(host);
|
|
}
|
|
|
|
protected boolean parseEmail(final String emailString) {
|
|
// check for null
|
|
if (emailString == null) {
|
|
return false;
|
|
}
|
|
|
|
// Check for an '@' character. Get the last one, in case the local part is
|
|
// quoted. See http://b/1944742.
|
|
final int atIndex = emailString.lastIndexOf('@');
|
|
if ((atIndex <= 0) || // no '@' character in the email address
|
|
// or @ on the first position
|
|
(atIndex == (emailString.length() - 1))) { // last character, no host
|
|
return false;
|
|
}
|
|
|
|
user = emailString.substring(0, atIndex);
|
|
host = emailString.substring(atIndex + 1);
|
|
|
|
return isValidInternal();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return user + "@" + host;
|
|
}
|
|
|
|
/**
|
|
* Ensure the email address is valid, conforming to current RFC2821 and
|
|
* RFC2822 guidelines (although some iffy characters, like ! and ;, are
|
|
* allowed because they are not technically prohibited in the RFC)
|
|
*/
|
|
private boolean isValidInternal() {
|
|
if ((user == null) || (host == null)) {
|
|
return false;
|
|
}
|
|
|
|
if ((user.length() == 0) || (host.length() == 0)) {
|
|
return false;
|
|
}
|
|
|
|
// check for white space in the host
|
|
if (ANY_WHITESPACE.indexIn(host) >= 0) {
|
|
return false;
|
|
}
|
|
|
|
// ensure the host is above the minimum length
|
|
if (host.length() < 4) {
|
|
return false;
|
|
}
|
|
|
|
final int firstDot = host.indexOf('.');
|
|
|
|
// ensure host contains at least one dot
|
|
if (firstDot == -1) {
|
|
return false;
|
|
}
|
|
|
|
// check if the host contains two continuous dots.
|
|
if (host.indexOf("..") >= 0) {
|
|
return false;
|
|
}
|
|
|
|
// check if the first host char is a dot.
|
|
if (host.charAt(0) == '.') {
|
|
return false;
|
|
}
|
|
|
|
final int secondDot = host.indexOf(".", firstDot + 1);
|
|
|
|
// if there's a dot at the end, there needs to be a second dot
|
|
if (host.charAt(host.length() - 1) == '.' && secondDot == -1) {
|
|
return false;
|
|
}
|
|
|
|
// Host must not have any disallowed characters; allowI18n dictates whether
|
|
// host must be ASCII.
|
|
if (!EMAIL_ALLOWED_CHARS.matchesAllOf(host)
|
|
|| (!allowI18n && !CharMatcher.ASCII.matchesAllOf(host))) {
|
|
return false;
|
|
}
|
|
|
|
if (user.startsWith("\"")) {
|
|
if (!isQuotedUserValid()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// check for white space in the user
|
|
if (ANY_WHITESPACE.indexIn(user) >= 0) {
|
|
return false;
|
|
}
|
|
|
|
// the user cannot contain two continuous dots
|
|
if (user.indexOf("..") >= 0) {
|
|
return false;
|
|
}
|
|
|
|
// User must not have any disallowed characters; allow I18n dictates whether
|
|
// user must be ASCII.
|
|
if (!EMAIL_ALLOWED_CHARS.matchesAllOf(user)
|
|
|| (!allowI18n && !CharMatcher.ASCII.matchesAllOf(user))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private boolean isQuotedUserValid() {
|
|
final int limit = user.length() - 1;
|
|
if (limit < 1 || !user.endsWith("\"")) {
|
|
return false;
|
|
}
|
|
|
|
// Unusual loop bounds (looking only at characters between the outer quotes,
|
|
// not at either quote character). Plus, i is manipulated within the loop.
|
|
for (int i = 1; i < limit; ++i) {
|
|
final char ch = user.charAt(i);
|
|
if (ch == '"' || ch == 127
|
|
// No non-whitespace control chars:
|
|
|| (ch < 32 && !ANY_WHITESPACE.matches(ch))
|
|
// No non-ASCII chars, unless i18n is in effect:
|
|
|| (ch >= 128 && !allowI18n)) {
|
|
return false;
|
|
} else if (ch == '\\') {
|
|
if (i + 1 < limit) {
|
|
++i; // Skip the quoted character
|
|
} else {
|
|
// We have a trailing backslash -- so it can't be quoting anything.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(final Object otherObject) {
|
|
// Do an instance check first as an optimization.
|
|
if (this == otherObject) {
|
|
return true;
|
|
}
|
|
if (otherObject instanceof EmailAddress) {
|
|
final EmailAddress otherAddress = (EmailAddress) otherObject;
|
|
return toString().equals(otherAddress.toString());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
// Arbitrary hash code as a function of both host and user.
|
|
return toString().hashCode();
|
|
}
|
|
|
|
// accessors
|
|
public boolean isValid() {
|
|
return valid;
|
|
}
|
|
|
|
public String getUser() {
|
|
return user;
|
|
}
|
|
|
|
public String getHost() {
|
|
return host;
|
|
}
|
|
|
|
// used to change the host on an email address and rechecks validity
|
|
|
|
/**
|
|
* Changes the host name of the email address and rechecks the address'
|
|
* validity. Exercise caution when storing EmailAddress instances in
|
|
* hash-keyed collections. Calling setHost() with a different host name will
|
|
* change the return value of hashCode.
|
|
*
|
|
* @param hostName The new host name of the email address.
|
|
*/
|
|
public void setHost(final String hostName) {
|
|
host = hostName;
|
|
valid = isValidInternal();
|
|
}
|
|
|
|
protected boolean valid = false;
|
|
protected String user = null;
|
|
protected String host = null;
|
|
protected boolean allowI18n = false;
|
|
}
|