• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * This file is part of the WebKit project.
3  *
4  * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com>
5  * Copyright (C) 2010 Google Inc. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23 
24 #include "config.h"
25 #include "core/html/forms/EmailInputType.h"
26 
27 #include "bindings/v8/ScriptRegexp.h"
28 #include "core/InputTypeNames.h"
29 #include "core/html/HTMLInputElement.h"
30 #include "core/html/parser/HTMLParserIdioms.h"
31 #include "core/page/Chrome.h"
32 #include "core/page/ChromeClient.h"
33 #include "platform/text/PlatformLocale.h"
34 #include "public/platform/Platform.h"
35 #include "wtf/PassOwnPtr.h"
36 #include "wtf/text/StringBuilder.h"
37 #include <unicode/idna.h>
38 #include <unicode/unistr.h>
39 
40 namespace WebCore {
41 
42 using blink::WebLocalizedString;
43 
44 // http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
45 static const char localPartCharacters[] = "abcdefghijklmnopqrstuvwxyz0123456789!#$%&'*+/=?^_`{|}~.-";
46 static const char emailPattern[] =
47     "[a-z0-9!#$%&'*+/=?^_`{|}~.-]+" // local part
48     "@"
49     "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?" // domain part
50     "(?:\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*";
51 
52 // RFC5321 says the maximum total length of a domain name is 255 octets.
53 static const int32_t maximumDomainNameLength = 255;
54 // Use the same option as in url/url_canon_icu.cc
55 static const int32_t idnaConversionOption = UIDNA_CHECK_BIDI;
56 
convertEmailAddressToASCII(const String & address)57 static String convertEmailAddressToASCII(const String& address)
58 {
59     if (address.containsOnlyASCII())
60         return address;
61 
62     size_t atPosition = address.find('@');
63     if (atPosition == kNotFound)
64         return address;
65 
66     // UnicodeString ctor for copy-on-write does not work reliably (in debug
67     // build.) TODO(jshin): In an unlikely case this is a perf-issue, treat
68     // 8bit and non-8bit strings separately.
69     icu::UnicodeString idnDomainName(address.charactersWithNullTermination().data() + atPosition + 1, address.length() - atPosition - 1);
70     icu::UnicodeString domainName;
71 
72     // Leak |idna| at the end.
73     UErrorCode errorCode = U_ZERO_ERROR;
74     static icu::IDNA *idna = icu::IDNA::createUTS46Instance(idnaConversionOption, errorCode);
75     ASSERT(idna);
76     icu::IDNAInfo idnaInfo;
77     idna->nameToASCII(idnDomainName, domainName, idnaInfo, errorCode);
78     if (U_FAILURE(errorCode) || idnaInfo.hasErrors() || domainName.length() > maximumDomainNameLength)
79         return address;
80 
81     StringBuilder builder;
82     builder.append(address, 0, atPosition + 1);
83     builder.append(domainName.getBuffer(), domainName.length());
84     return builder.toString();
85 }
86 
convertEmailAddressToUnicode(const String & address) const87 String EmailInputType::convertEmailAddressToUnicode(const String& address) const
88 {
89     if (!address.containsOnlyASCII())
90         return address;
91 
92     size_t atPosition = address.find('@');
93     if (atPosition == kNotFound)
94         return address;
95 
96     if (address.find("xn--", atPosition + 1) == kNotFound)
97         return address;
98 
99     if (!chrome())
100         return address;
101 
102     String languages = chrome()->client().acceptLanguages();
103     String unicodeHost = blink::Platform::current()->convertIDNToUnicode(address.substring(atPosition + 1), languages);
104     StringBuilder builder;
105     builder.append(address, 0, atPosition + 1);
106     builder.append(unicodeHost);
107     return builder.toString();
108 }
109 
isInvalidLocalPartCharacter(UChar ch)110 static bool isInvalidLocalPartCharacter(UChar ch)
111 {
112     if (!isASCII(ch))
113         return true;
114     DEFINE_STATIC_LOCAL(const String, validCharacters, (localPartCharacters));
115     return validCharacters.find(toASCIILower(ch)) == kNotFound;
116 }
117 
isInvalidDomainCharacter(UChar ch)118 static bool isInvalidDomainCharacter(UChar ch)
119 {
120     if (!isASCII(ch))
121         return true;
122     return !isASCIILower(ch) && !isASCIIUpper(ch) && !isASCIIDigit(ch) && ch != '.' && ch != '-';
123 }
124 
checkValidDotUsage(const String & domain)125 static bool checkValidDotUsage(const String& domain)
126 {
127     if (domain.isEmpty())
128         return true;
129     if (domain[0] == '.' || domain[domain.length() - 1] == '.')
130         return false;
131     return domain.find("..") == kNotFound;
132 }
133 
isValidEmailAddress(const String & address)134 static bool isValidEmailAddress(const String& address)
135 {
136     int addressLength = address.length();
137     if (!addressLength)
138         return false;
139 
140     DEFINE_STATIC_LOCAL(const ScriptRegexp, regExp, (emailPattern, TextCaseInsensitive));
141 
142     int matchLength;
143     int matchOffset = regExp.match(address, 0, &matchLength);
144 
145     return !matchOffset && matchLength == addressLength;
146 }
147 
create(HTMLInputElement & element)148 PassRefPtrWillBeRawPtr<InputType> EmailInputType::create(HTMLInputElement& element)
149 {
150     return adoptRefWillBeNoop(new EmailInputType(element));
151 }
152 
countUsage()153 void EmailInputType::countUsage()
154 {
155     countUsageIfVisible(UseCounter::InputTypeEmail);
156     bool hasMaxLength = element().fastHasAttribute(HTMLNames::maxlengthAttr);
157     if (hasMaxLength)
158         countUsageIfVisible(UseCounter::InputTypeEmailMaxLength);
159     if (element().multiple()) {
160         countUsageIfVisible(UseCounter::InputTypeEmailMultiple);
161         if (hasMaxLength)
162             countUsageIfVisible(UseCounter::InputTypeEmailMultipleMaxLength);
163     }
164 }
165 
formControlType() const166 const AtomicString& EmailInputType::formControlType() const
167 {
168     return InputTypeNames::email;
169 }
170 
171 // The return value is an invalid email address string if the specified string
172 // contains an invalid email address. Otherwise, null string is returned.
173 // If an empty string is returned, it means empty address is specified.
174 // e.g. "foo@example.com,,bar@example.com" for multiple case.
findInvalidAddress(const String & value) const175 String EmailInputType::findInvalidAddress(const String& value) const
176 {
177     if (value.isEmpty())
178         return String();
179     if (!element().multiple())
180         return isValidEmailAddress(value) ? String() : value;
181     Vector<String> addresses;
182     value.split(',', true, addresses);
183     for (unsigned i = 0; i < addresses.size(); ++i) {
184         String stripped = stripLeadingAndTrailingHTMLSpaces(addresses[i]);
185         if (!isValidEmailAddress(stripped))
186             return stripped;
187     }
188     return String();
189 }
190 
typeMismatchFor(const String & value) const191 bool EmailInputType::typeMismatchFor(const String& value) const
192 {
193     return !findInvalidAddress(value).isNull();
194 }
195 
typeMismatch() const196 bool EmailInputType::typeMismatch() const
197 {
198     return typeMismatchFor(element().value());
199 }
200 
typeMismatchText() const201 String EmailInputType::typeMismatchText() const
202 {
203     String invalidAddress = findInvalidAddress(element().value());
204     ASSERT(!invalidAddress.isNull());
205     if (invalidAddress.isEmpty())
206         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmpty);
207     String atSign = String("@");
208     size_t atIndex = invalidAddress.find('@');
209     if (atIndex == kNotFound)
210         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailNoAtSign, atSign, invalidAddress);
211     // We check validity against an ASCII value because of difficulty to check
212     // invalid characters. However we should show Unicode value.
213     String unicodeAddress = convertEmailAddressToUnicode(invalidAddress);
214     String localPart = invalidAddress.left(atIndex);
215     String domain = invalidAddress.substring(atIndex + 1);
216     if (localPart.isEmpty())
217         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmptyLocal, atSign, unicodeAddress);
218     if (domain.isEmpty())
219         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmptyDomain, atSign, unicodeAddress);
220     size_t invalidCharIndex = localPart.find(isInvalidLocalPartCharacter);
221     if (invalidCharIndex != kNotFound) {
222         unsigned charLength = U_IS_LEAD(localPart[invalidCharIndex]) ? 2 : 1;
223         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidLocal, atSign, localPart.substring(invalidCharIndex, charLength));
224     }
225     invalidCharIndex = domain.find(isInvalidDomainCharacter);
226     if (invalidCharIndex != kNotFound) {
227         unsigned charLength = U_IS_LEAD(domain[invalidCharIndex]) ? 2 : 1;
228         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidDomain, atSign, domain.substring(invalidCharIndex, charLength));
229     }
230     if (!checkValidDotUsage(domain)) {
231         size_t atIndexInUnicode = unicodeAddress.find('@');
232         ASSERT(atIndexInUnicode != kNotFound);
233         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidDots, String("."), unicodeAddress.substring(atIndexInUnicode + 1));
234     }
235     if (element().multiple())
236         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForMultipleEmail);
237     return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmail);
238 }
239 
isEmailField() const240 bool EmailInputType::isEmailField() const
241 {
242     return true;
243 }
244 
supportsSelectionAPI() const245 bool EmailInputType::supportsSelectionAPI() const
246 {
247     return false;
248 }
249 
sanitizeValue(const String & proposedValue) const250 String EmailInputType::sanitizeValue(const String& proposedValue) const
251 {
252     String noLineBreakValue = proposedValue.removeCharacters(isHTMLLineBreak);
253     if (!element().multiple())
254         return stripLeadingAndTrailingHTMLSpaces(noLineBreakValue);
255     Vector<String> addresses;
256     noLineBreakValue.split(',', true, addresses);
257     StringBuilder strippedValue;
258     for (size_t i = 0; i < addresses.size(); ++i) {
259         if (i > 0)
260             strippedValue.append(",");
261         strippedValue.append(stripLeadingAndTrailingHTMLSpaces(addresses[i]));
262     }
263     return strippedValue.toString();
264 }
265 
convertFromVisibleValue(const String & visibleValue) const266 String EmailInputType::convertFromVisibleValue(const String& visibleValue) const
267 {
268     String sanitizedValue = sanitizeValue(visibleValue);
269     if (!element().multiple())
270         return convertEmailAddressToASCII(sanitizedValue);
271     Vector<String> addresses;
272     sanitizedValue.split(',', true, addresses);
273     StringBuilder builder;
274     builder.reserveCapacity(sanitizedValue.length());
275     for (size_t i = 0; i < addresses.size(); ++i) {
276         if (i > 0)
277             builder.append(",");
278         builder.append(convertEmailAddressToASCII(addresses[i]));
279     }
280     return builder.toString();
281 }
282 
visibleValue() const283 String EmailInputType::visibleValue() const
284 {
285     String value = element().value();
286     if (!element().multiple())
287         return convertEmailAddressToUnicode(value);
288 
289     Vector<String> addresses;
290     value.split(',', true, addresses);
291     StringBuilder builder;
292     builder.reserveCapacity(value.length());
293     for (size_t i = 0; i < addresses.size(); ++i) {
294         if (i > 0)
295             builder.append(",");
296         builder.append(convertEmailAddressToUnicode(addresses[i]));
297     }
298     return builder.toString();
299 }
300 
301 } // namespace WebCore
302