1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.common; 18 19 import android.text.TextUtils; 20 import android.text.util.Rfc822Token; 21 import android.text.util.Rfc822Tokenizer; 22 import android.util.Patterns; 23 import android.widget.AutoCompleteTextView; 24 25 import java.util.regex.Pattern; 26 27 /** 28 * This class works as a Validator for AutoCompleteTextView for 29 * email addresses. If a token does not appear to be a valid address, 30 * it is trimmed of characters that cannot legitimately appear in one 31 * and has the specified domain name added. It is meant for use with 32 * {@link Rfc822Token} and {@link Rfc822Tokenizer}. 33 * 34 * @deprecated In the future make sure we don't quietly alter the user's 35 * text in ways they did not intend. Meanwhile, hide this 36 * class from the public API because it does not even have 37 * a full understanding of the syntax it claims to correct. 38 * @hide 39 */ 40 @Deprecated 41 public class Rfc822Validator implements AutoCompleteTextView.Validator { 42 /** 43 * Expression that matches the local part of an email address. 44 * This expression does not follow the constraints of the RFC towards the dots, because the 45 * de facto standard is to allow them anywhere. 46 * 47 * It is however a simplification and it will not validate the double-quote syntax. 48 */ 49 private static final String EMAIL_ADDRESS_LOCALPART_REGEXP = 50 "((?!\\s)[\\.\\w!#$%&'*+\\-/=?^`{|}~\u0080-\uFFFE])+"; 51 52 /** 53 * Alias of characters that can be used in IRI, as per RFC 3987. 54 */ 55 private static final String GOOD_IRI_CHAR = Patterns.GOOD_IRI_CHAR; 56 57 /** 58 * Regular expression for a domain label, as per RFC 3490. 59 * Its total length must not exceed 63 octets, according to RFC 5890. 60 */ 61 private static final String LABEL_REGEXP = 62 "([" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,61})?[" + GOOD_IRI_CHAR + "]"; 63 64 /** 65 * Expression that matches a domain name, including international domain names in Punycode or 66 * Unicode. 67 */ 68 private static final String DOMAIN_REGEXP = 69 "("+ LABEL_REGEXP + "\\.)+" // Subdomains and domain 70 // Top-level domain must be at least 2 chars 71 + "[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]"; 72 73 /** 74 * Pattern for an email address. 75 * 76 * It is similar to {@link android.util.Patterns#EMAIL_ADDRESS}, but also accepts Unicode 77 * characters. 78 */ 79 private static final Pattern EMAIL_ADDRESS_PATTERN = 80 Pattern.compile(EMAIL_ADDRESS_LOCALPART_REGEXP + "@" + DOMAIN_REGEXP); 81 82 private String mDomain; 83 private boolean mRemoveInvalid = false; 84 85 /** 86 * Constructs a new validator that uses the specified domain name as 87 * the default when none is specified. 88 */ 89 public Rfc822Validator(String domain) { 90 mDomain = domain; 91 } 92 93 /** 94 * {@inheritDoc} 95 */ 96 public boolean isValid(CharSequence text) { 97 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text); 98 return tokens.length == 1 && 99 EMAIL_ADDRESS_PATTERN. 100 matcher(tokens[0].getAddress()).matches(); 101 } 102 103 /** 104 * Specify if the validator should remove invalid tokens instead of trying 105 * to fix them. This can be used to strip results of incorrectly formatted 106 * tokens. 107 * 108 * @param remove true to remove tokens with the wrong format, false to 109 * attempt to fix them 110 */ 111 public void setRemoveInvalid(boolean remove) { 112 mRemoveInvalid = remove; 113 } 114 115 /** 116 * @return a string in which all the characters that are illegal for the username 117 * or the domain name part of the email address have been removed. 118 */ 119 private String removeIllegalCharacters(String s) { 120 StringBuilder result = new StringBuilder(); 121 int length = s.length(); 122 for (int i = 0; i < length; i++) { 123 char c = s.charAt(i); 124 125 /* 126 * An RFC822 atom can contain any ASCII printing character 127 * except for periods and any of the following punctuation. 128 * A local-part can contain multiple atoms, concatenated by 129 * periods, so do allow periods here. 130 */ 131 132 if (c <= ' ' || c > '~') { 133 continue; 134 } 135 136 if (c == '(' || c == ')' || c == '<' || c == '>' || 137 c == '@' || c == ',' || c == ';' || c == ':' || 138 c == '\\' || c == '"' || c == '[' || c == ']') { 139 continue; 140 } 141 142 result.append(c); 143 } 144 return result.toString(); 145 } 146 147 /** 148 * {@inheritDoc} 149 */ 150 public CharSequence fixText(CharSequence cs) { 151 // Return an empty string if the email address only contains spaces, \n or \t 152 if (TextUtils.getTrimmedLength(cs) == 0) return ""; 153 154 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs); 155 StringBuilder sb = new StringBuilder(); 156 157 for (int i = 0; i < tokens.length; i++) { 158 String text = tokens[i].getAddress(); 159 160 if (mRemoveInvalid && !isValid(text)) { 161 continue; 162 } 163 int index = text.indexOf('@'); 164 if (index < 0) { 165 // append the domain of the account if it exists 166 if (mDomain != null) { 167 tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain); 168 } 169 } else { 170 // Otherwise, remove the illegal characters on both sides of the '@' 171 String fix = removeIllegalCharacters(text.substring(0, index)); 172 if (TextUtils.isEmpty(fix)) { 173 // if the address is empty after removing invalid chars 174 // don't use it 175 continue; 176 } 177 String domain = removeIllegalCharacters(text.substring(index + 1)); 178 boolean emptyDomain = domain.length() == 0; 179 if (!emptyDomain || mDomain != null) { 180 tokens[i].setAddress(fix + "@" + (!emptyDomain ? domain : mDomain)); 181 } 182 } 183 184 sb.append(tokens[i].toString()); 185 if (i + 1 < tokens.length) { 186 sb.append(", "); 187 } 188 } 189 190 return sb; 191 } 192 } 193