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
17package com.android.common;
18
19import android.text.TextUtils;
20import android.text.util.Rfc822Token;
21import android.text.util.Rfc822Tokenizer;
22import android.widget.AutoCompleteTextView;
23
24import java.util.regex.Pattern;
25
26/**
27 * This class works as a Validator for AutoCompleteTextView for
28 * email addresses.  If a token does not appear to be a valid address,
29 * it is trimmed of characters that cannot legitimately appear in one
30 * and has the specified domain name added.  It is meant for use with
31 * {@link Rfc822Token} and {@link Rfc822Tokenizer}.
32 *
33 * @deprecated In the future make sure we don't quietly alter the user's
34 *             text in ways they did not intend.  Meanwhile, hide this
35 *             class from the public API because it does not even have
36 *             a full understanding of the syntax it claims to correct.
37 * @hide
38 */
39@Deprecated
40public class Rfc822Validator implements AutoCompleteTextView.Validator {
41    /*
42     * Regex.EMAIL_ADDRESS_PATTERN hardcodes the TLD that we accept, but we
43     * want to make sure we will keep accepting email addresses with TLD's
44     * that don't exist at the time of this writing, so this regexp relaxes
45     * that constraint by accepting any kind of top level domain, not just
46     * ".com", ".fr", etc...
47     */
48    private static final Pattern EMAIL_ADDRESS_PATTERN =
49            Pattern.compile("[^\\s@]+@([^\\s@\\.]+\\.)+[a-zA-z][a-zA-Z][a-zA-Z]*");
50
51    private String mDomain;
52    private boolean mRemoveInvalid = false;
53
54    /**
55     * Constructs a new validator that uses the specified domain name as
56     * the default when none is specified.
57     */
58    public Rfc822Validator(String domain) {
59        mDomain = domain;
60    }
61
62    /**
63     * {@inheritDoc}
64     */
65    public boolean isValid(CharSequence text) {
66        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text);
67
68        return tokens.length == 1 &&
69               EMAIL_ADDRESS_PATTERN.
70                   matcher(tokens[0].getAddress()).matches();
71    }
72
73    /**
74     * Specify if the validator should remove invalid tokens instead of trying
75     * to fix them. This can be used to strip results of incorrectly formatted
76     * tokens.
77     *
78     * @param remove true to remove tokens with the wrong format, false to
79     *            attempt to fix them
80     */
81    public void setRemoveInvalid(boolean remove) {
82        mRemoveInvalid = remove;
83    }
84
85    /**
86     * @return a string in which all the characters that are illegal for the username
87     * or the domain name part of the email address have been removed.
88     */
89    private String removeIllegalCharacters(String s) {
90        StringBuilder result = new StringBuilder();
91        int length = s.length();
92        for (int i = 0; i < length; i++) {
93            char c = s.charAt(i);
94
95            /*
96             * An RFC822 atom can contain any ASCII printing character
97             * except for periods and any of the following punctuation.
98             * A local-part can contain multiple atoms, concatenated by
99             * periods, so do allow periods here.
100             */
101
102            if (c <= ' ' || c > '~') {
103                continue;
104            }
105
106            if (c == '(' || c == ')' || c == '<' || c == '>' ||
107                c == '@' || c == ',' || c == ';' || c == ':' ||
108                c == '\\' || c == '"' || c == '[' || c == ']') {
109                continue;
110            }
111
112            result.append(c);
113        }
114        return result.toString();
115    }
116
117    /**
118     * {@inheritDoc}
119     */
120    public CharSequence fixText(CharSequence cs) {
121        // Return an empty string if the email address only contains spaces, \n or \t
122        if (TextUtils.getTrimmedLength(cs) == 0) return "";
123
124        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs);
125        StringBuilder sb = new StringBuilder();
126
127        for (int i = 0; i < tokens.length; i++) {
128            String text = tokens[i].getAddress();
129
130            if (mRemoveInvalid && !isValid(text)) {
131                continue;
132            }
133            int index = text.indexOf('@');
134            if (index < 0) {
135                // append the domain of the account if it exists
136                if (mDomain != null) {
137                    tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain);
138                }
139            } else {
140                // Otherwise, remove the illegal characters on both sides of the '@'
141                String fix = removeIllegalCharacters(text.substring(0, index));
142                if (TextUtils.isEmpty(fix)) {
143                    // if the address is empty after removing invalid chars
144                    // don't use it
145                    continue;
146                }
147                String domain = removeIllegalCharacters(text.substring(index + 1));
148                boolean emptyDomain = domain.length() == 0;
149                if (!emptyDomain || mDomain != null) {
150                    tokens[i].setAddress(fix + "@" + (!emptyDomain ? domain : mDomain));
151                }
152            }
153
154            sb.append(tokens[i].toString());
155            if (i + 1 < tokens.length) {
156                sb.append(", ");
157            }
158        }
159
160        return sb;
161    }
162}
163