Home | History | Annotate | Download | only in common
      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