2 * This file is part of the WebKit project.
4 * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com>
5 * Copyright (C) 2010 Google Inc. All rights reserved.
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.
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.
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.
25 #include "core/html/forms/EmailInputType.h"
27 #include "bindings/core/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>
42 using blink::WebLocalizedString;
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
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])?)*";
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;
57 static String convertEmailAddressToASCII(const String& address)
59 if (address.containsOnlyASCII())
62 size_t atPosition = address.find('@');
63 if (atPosition == kNotFound)
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;
72 // Leak |idna| at the end.
73 UErrorCode errorCode = U_ZERO_ERROR;
74 static icu::IDNA *idna = icu::IDNA::createUTS46Instance(idnaConversionOption, errorCode);
76 icu::IDNAInfo idnaInfo;
77 idna->nameToASCII(idnDomainName, domainName, idnaInfo, errorCode);
78 if (U_FAILURE(errorCode) || idnaInfo.hasErrors() || domainName.length() > maximumDomainNameLength)
81 StringBuilder builder;
82 builder.append(address, 0, atPosition + 1);
83 builder.append(domainName.getBuffer(), domainName.length());
84 return builder.toString();
87 String EmailInputType::convertEmailAddressToUnicode(const String& address) const
89 if (!address.containsOnlyASCII())
92 size_t atPosition = address.find('@');
93 if (atPosition == kNotFound)
96 if (address.find("xn--", atPosition + 1) == kNotFound)
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();
110 static bool isInvalidLocalPartCharacter(UChar ch)
114 DEFINE_STATIC_LOCAL(const String, validCharacters, (localPartCharacters));
115 return validCharacters.find(toASCIILower(ch)) == kNotFound;
118 static bool isInvalidDomainCharacter(UChar ch)
122 return !isASCIILower(ch) && !isASCIIUpper(ch) && !isASCIIDigit(ch) && ch != '.' && ch != '-';
125 static bool checkValidDotUsage(const String& domain)
127 if (domain.isEmpty())
129 if (domain[0] == '.' || domain[domain.length() - 1] == '.')
131 return domain.find("..") == kNotFound;
134 static bool isValidEmailAddress(const String& address)
136 int addressLength = address.length();
140 DEFINE_STATIC_LOCAL(const ScriptRegexp, regExp, (emailPattern, TextCaseInsensitive));
143 int matchOffset = regExp.match(address, 0, &matchLength);
145 return !matchOffset && matchLength == addressLength;
148 PassRefPtrWillBeRawPtr<InputType> EmailInputType::create(HTMLInputElement& element)
150 return adoptRefWillBeNoop(new EmailInputType(element));
153 void EmailInputType::countUsage()
155 countUsageIfVisible(UseCounter::InputTypeEmail);
156 bool hasMaxLength = element().fastHasAttribute(HTMLNames::maxlengthAttr);
158 countUsageIfVisible(UseCounter::InputTypeEmailMaxLength);
159 if (element().multiple()) {
160 countUsageIfVisible(UseCounter::InputTypeEmailMultiple);
162 countUsageIfVisible(UseCounter::InputTypeEmailMultipleMaxLength);
166 const AtomicString& EmailInputType::formControlType() const
168 return InputTypeNames::email;
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.
175 String EmailInputType::findInvalidAddress(const String& value) const
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))
191 bool EmailInputType::typeMismatchFor(const String& value) const
193 return !findInvalidAddress(value).isNull();
196 bool EmailInputType::typeMismatch() const
198 return typeMismatchFor(element().value());
201 String EmailInputType::typeMismatchText() const
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));
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));
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));
235 if (element().multiple())
236 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForMultipleEmail);
237 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmail);
240 bool EmailInputType::supportsSelectionAPI() const
245 String EmailInputType::sanitizeValue(const String& proposedValue) const
247 String noLineBreakValue = proposedValue.removeCharacters(isHTMLLineBreak);
248 if (!element().multiple())
249 return stripLeadingAndTrailingHTMLSpaces(noLineBreakValue);
250 Vector<String> addresses;
251 noLineBreakValue.split(',', true, addresses);
252 StringBuilder strippedValue;
253 for (size_t i = 0; i < addresses.size(); ++i) {
255 strippedValue.append(',');
256 strippedValue.append(stripLeadingAndTrailingHTMLSpaces(addresses[i]));
258 return strippedValue.toString();
261 String EmailInputType::convertFromVisibleValue(const String& visibleValue) const
263 String sanitizedValue = sanitizeValue(visibleValue);
264 if (!element().multiple())
265 return convertEmailAddressToASCII(sanitizedValue);
266 Vector<String> addresses;
267 sanitizedValue.split(',', true, addresses);
268 StringBuilder builder;
269 builder.reserveCapacity(sanitizedValue.length());
270 for (size_t i = 0; i < addresses.size(); ++i) {
273 builder.append(convertEmailAddressToASCII(addresses[i]));
275 return builder.toString();
278 String EmailInputType::visibleValue() const
280 String value = element().value();
281 if (!element().multiple())
282 return convertEmailAddressToUnicode(value);
284 Vector<String> addresses;
285 value.split(',', true, addresses);
286 StringBuilder builder;
287 builder.reserveCapacity(value.length());
288 for (size_t i = 0; i < addresses.size(); ++i) {
291 builder.append(convertEmailAddressToUnicode(addresses[i]));
293 return builder.toString();