1823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang/*
2823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * Copyright (C) 2008 The Android Open Source Project
3823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang *
4823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * Licensed under the Apache License, Version 2.0 (the "License");
5823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * you may not use this file except in compliance with the License.
6823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * You may obtain a copy of the License at
7823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang *
8823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang *      http://www.apache.org/licenses/LICENSE-2.0
9823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang *
10823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * Unless required by applicable law or agreed to in writing, software
11823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * distributed under the License is distributed on an "AS IS" BASIS,
12823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * See the License for the specific language governing permissions and
14823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * limitations under the License.
15823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang */
16823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
17823b6f3516076b92f78c3fc27037d24bb514e653Ying Wangpackage com.android.common;
18823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
19823b6f3516076b92f78c3fc27037d24bb514e653Ying Wangimport android.text.TextUtils;
20823b6f3516076b92f78c3fc27037d24bb514e653Ying Wangimport android.text.util.Rfc822Token;
21823b6f3516076b92f78c3fc27037d24bb514e653Ying Wangimport android.text.util.Rfc822Tokenizer;
224c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décampsimport android.util.Patterns;
23823b6f3516076b92f78c3fc27037d24bb514e653Ying Wangimport android.widget.AutoCompleteTextView;
24823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
25823b6f3516076b92f78c3fc27037d24bb514e653Ying Wangimport java.util.regex.Pattern;
26823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
27823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang/**
28823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * This class works as a Validator for AutoCompleteTextView for
29823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * email addresses.  If a token does not appear to be a valid address,
30823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * it is trimmed of characters that cannot legitimately appear in one
31823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * and has the specified domain name added.  It is meant for use with
32823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * {@link Rfc822Token} and {@link Rfc822Tokenizer}.
33823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang *
34823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * @deprecated In the future make sure we don't quietly alter the user's
35823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang *             text in ways they did not intend.  Meanwhile, hide this
36823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang *             class from the public API because it does not even have
37823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang *             a full understanding of the syntax it claims to correct.
38823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang * @hide
39823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang */
40490556a764a879cd0eaff358e90705cc1335c92eErik@Deprecated
41823b6f3516076b92f78c3fc27037d24bb514e653Ying Wangpublic class Rfc822Validator implements AutoCompleteTextView.Validator {
424c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    /**
434c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * Expression that matches the local part of an email address.
444c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * This expression does not follow the constraints of the RFC towards the dots, because the
454c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * de facto standard is to allow them anywhere.
464c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     *
474c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * It is however a simplification and it will not validate the double-quote syntax.
484c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     */
494c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    private static final String EMAIL_ADDRESS_LOCALPART_REGEXP =
504c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps        "((?!\\s)[\\.\\w!#$%&'*+\\-/=?^`{|}~\u0080-\uFFFE])+";
514c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps
524c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    /**
534c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * Alias of characters that can be used in IRI, as per RFC 3987.
544c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     */
554c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    private static final String GOOD_IRI_CHAR = Patterns.GOOD_IRI_CHAR;
564c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps
574c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    /**
584c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * Regular expression for a domain label, as per RFC 3490.
594c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * Its total length must not exceed 63 octets, according to RFC 5890.
604c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     */
614c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    private static final String LABEL_REGEXP =
624c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps        "([" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,61})?[" + GOOD_IRI_CHAR + "]";
634c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps
644c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    /**
654c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * Expression that matches a domain name, including international domain names in Punycode or
664c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * Unicode.
674c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     */
684c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    private static final String DOMAIN_REGEXP =
694c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps        "("+ LABEL_REGEXP + "\\.)+"                 // Subdomains and domain
704c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps        // Top-level domain must be at least 2 chars
714c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps        + "[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]";
724c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps
734c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps    /**
744c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * Pattern for an email address.
754c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     *
764c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * It is similar to {@link android.util.Patterns#EMAIL_ADDRESS}, but also accepts Unicode
774c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps     * characters.
78823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     */
79823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    private static final Pattern EMAIL_ADDRESS_PATTERN =
804c384ab3ae7621e602a95aeac6e527a52bb5598cRégis Décamps            Pattern.compile(EMAIL_ADDRESS_LOCALPART_REGEXP + "@" + DOMAIN_REGEXP);
81823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
82823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    private String mDomain;
83490556a764a879cd0eaff358e90705cc1335c92eErik    private boolean mRemoveInvalid = false;
84823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
85823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    /**
86823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     * Constructs a new validator that uses the specified domain name as
87823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     * the default when none is specified.
88823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     */
89823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    public Rfc822Validator(String domain) {
90823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        mDomain = domain;
91823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    }
92823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
93823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    /**
94823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     * {@inheritDoc}
95823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     */
96823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    public boolean isValid(CharSequence text) {
97823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text);
98823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        return tokens.length == 1 &&
99823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang               EMAIL_ADDRESS_PATTERN.
100823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                   matcher(tokens[0].getAddress()).matches();
101823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    }
102823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
103823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    /**
104490556a764a879cd0eaff358e90705cc1335c92eErik     * Specify if the validator should remove invalid tokens instead of trying
105490556a764a879cd0eaff358e90705cc1335c92eErik     * to fix them. This can be used to strip results of incorrectly formatted
106490556a764a879cd0eaff358e90705cc1335c92eErik     * tokens.
107490556a764a879cd0eaff358e90705cc1335c92eErik     *
108490556a764a879cd0eaff358e90705cc1335c92eErik     * @param remove true to remove tokens with the wrong format, false to
109490556a764a879cd0eaff358e90705cc1335c92eErik     *            attempt to fix them
110490556a764a879cd0eaff358e90705cc1335c92eErik     */
111490556a764a879cd0eaff358e90705cc1335c92eErik    public void setRemoveInvalid(boolean remove) {
112490556a764a879cd0eaff358e90705cc1335c92eErik        mRemoveInvalid = remove;
113490556a764a879cd0eaff358e90705cc1335c92eErik    }
114490556a764a879cd0eaff358e90705cc1335c92eErik
115490556a764a879cd0eaff358e90705cc1335c92eErik    /**
116823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     * @return a string in which all the characters that are illegal for the username
117823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     * or the domain name part of the email address have been removed.
118823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     */
119823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    private String removeIllegalCharacters(String s) {
120823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        StringBuilder result = new StringBuilder();
121823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        int length = s.length();
122823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        for (int i = 0; i < length; i++) {
123823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            char c = s.charAt(i);
124823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
125823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            /*
126823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang             * An RFC822 atom can contain any ASCII printing character
127823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang             * except for periods and any of the following punctuation.
128823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang             * A local-part can contain multiple atoms, concatenated by
129823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang             * periods, so do allow periods here.
130823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang             */
131823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
132823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            if (c <= ' ' || c > '~') {
133823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                continue;
134823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            }
135823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
136823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            if (c == '(' || c == ')' || c == '<' || c == '>' ||
137823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                c == '@' || c == ',' || c == ';' || c == ':' ||
138823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                c == '\\' || c == '"' || c == '[' || c == ']') {
139823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                continue;
140823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            }
141823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
142823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            result.append(c);
143823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        }
144823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        return result.toString();
145823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    }
146823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
147823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    /**
148823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     * {@inheritDoc}
149823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang     */
150823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    public CharSequence fixText(CharSequence cs) {
151823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        // Return an empty string if the email address only contains spaces, \n or \t
152823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        if (TextUtils.getTrimmedLength(cs) == 0) return "";
153823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
154823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs);
155823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        StringBuilder sb = new StringBuilder();
156823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
157823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        for (int i = 0; i < tokens.length; i++) {
158823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            String text = tokens[i].getAddress();
159490556a764a879cd0eaff358e90705cc1335c92eErik
160490556a764a879cd0eaff358e90705cc1335c92eErik            if (mRemoveInvalid && !isValid(text)) {
161490556a764a879cd0eaff358e90705cc1335c92eErik                continue;
162490556a764a879cd0eaff358e90705cc1335c92eErik            }
163823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            int index = text.indexOf('@');
164823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            if (index < 0) {
165490556a764a879cd0eaff358e90705cc1335c92eErik                // append the domain of the account if it exists
166490556a764a879cd0eaff358e90705cc1335c92eErik                if (mDomain != null) {
167490556a764a879cd0eaff358e90705cc1335c92eErik                    tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain);
168490556a764a879cd0eaff358e90705cc1335c92eErik                }
169823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            } else {
170823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                // Otherwise, remove the illegal characters on both sides of the '@'
171823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                String fix = removeIllegalCharacters(text.substring(0, index));
172490556a764a879cd0eaff358e90705cc1335c92eErik                if (TextUtils.isEmpty(fix)) {
173490556a764a879cd0eaff358e90705cc1335c92eErik                    // if the address is empty after removing invalid chars
174490556a764a879cd0eaff358e90705cc1335c92eErik                    // don't use it
175490556a764a879cd0eaff358e90705cc1335c92eErik                    continue;
176490556a764a879cd0eaff358e90705cc1335c92eErik                }
177823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                String domain = removeIllegalCharacters(text.substring(index + 1));
178490556a764a879cd0eaff358e90705cc1335c92eErik                boolean emptyDomain = domain.length() == 0;
179490556a764a879cd0eaff358e90705cc1335c92eErik                if (!emptyDomain || mDomain != null) {
180490556a764a879cd0eaff358e90705cc1335c92eErik                    tokens[i].setAddress(fix + "@" + (!emptyDomain ? domain : mDomain));
181490556a764a879cd0eaff358e90705cc1335c92eErik                }
182823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            }
183823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
184823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            sb.append(tokens[i].toString());
185823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            if (i + 1 < tokens.length) {
186823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang                sb.append(", ");
187823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang            }
188823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        }
189823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang
190823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang        return sb;
191823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang    }
192823b6f3516076b92f78c3fc27037d24bb514e653Ying Wang}
193