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 */
39public class Rfc822Validator implements AutoCompleteTextView.Validator {
40    /*
41     * Regex.EMAIL_ADDRESS_PATTERN hardcodes the TLD that we accept, but we
42     * want to make sure we will keep accepting email addresses with TLD's
43     * that don't exist at the time of this writing, so this regexp relaxes
44     * that constraint by accepting any kind of top level domain, not just
45     * ".com", ".fr", etc...
46     */
47    private static final Pattern EMAIL_ADDRESS_PATTERN =
48            Pattern.compile("[^\\s@]+@([^\\s@\\.]+\\.)+[a-zA-z][a-zA-Z][a-zA-Z]*");
49
50    private String mDomain;
51
52    /**
53     * Constructs a new validator that uses the specified domain name as
54     * the default when none is specified.
55     */
56    public Rfc822Validator(String domain) {
57        mDomain = domain;
58    }
59
60    /**
61     * {@inheritDoc}
62     */
63    public boolean isValid(CharSequence text) {
64        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text);
65
66        return tokens.length == 1 &&
67               EMAIL_ADDRESS_PATTERN.
68                   matcher(tokens[0].getAddress()).matches();
69    }
70
71    /**
72     * @return a string in which all the characters that are illegal for the username
73     * or the domain name part of the email address have been removed.
74     */
75    private String removeIllegalCharacters(String s) {
76        StringBuilder result = new StringBuilder();
77        int length = s.length();
78        for (int i = 0; i < length; i++) {
79            char c = s.charAt(i);
80
81            /*
82             * An RFC822 atom can contain any ASCII printing character
83             * except for periods and any of the following punctuation.
84             * A local-part can contain multiple atoms, concatenated by
85             * periods, so do allow periods here.
86             */
87
88            if (c <= ' ' || c > '~') {
89                continue;
90            }
91
92            if (c == '(' || c == ')' || c == '<' || c == '>' ||
93                c == '@' || c == ',' || c == ';' || c == ':' ||
94                c == '\\' || c == '"' || c == '[' || c == ']') {
95                continue;
96            }
97
98            result.append(c);
99        }
100        return result.toString();
101    }
102
103    /**
104     * {@inheritDoc}
105     */
106    public CharSequence fixText(CharSequence cs) {
107        // Return an empty string if the email address only contains spaces, \n or \t
108        if (TextUtils.getTrimmedLength(cs) == 0) return "";
109
110        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs);
111        StringBuilder sb = new StringBuilder();
112
113        for (int i = 0; i < tokens.length; i++) {
114            String text = tokens[i].getAddress();
115            int index = text.indexOf('@');
116            if (index < 0) {
117                // If there is no @, just append the domain of the account
118                tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain);
119            } else {
120                // Otherwise, remove the illegal characters on both sides of the '@'
121                String fix = removeIllegalCharacters(text.substring(0, index));
122                String domain = removeIllegalCharacters(text.substring(index + 1));
123                tokens[i].setAddress(fix + "@" + (domain.length() != 0 ? domain : mDomain));
124            }
125
126            sb.append(tokens[i].toString());
127            if (i + 1 < tokens.length) {
128                sb.append(", ");
129            }
130        }
131
132        return sb;
133    }
134}
135