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 "InputTypeNames.h"
28 #include "bindings/v8/ScriptRegexp.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/uidna.h>
41 using blink::WebLocalizedString;
43 // http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
44 static const char localPartCharacters[] = "abcdefghijklmnopqrstuvwxyz0123456789!#$%&'*+/=?^_`{|}~.-";
45 static const char emailPattern[] =
46 "[a-z0-9!#$%&'*+/=?^_`{|}~.-]+" // local part
48 "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?" // domain part
49 "(?:\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*";
51 // RFC5321 says the maximum total length of a domain name is 255 octets.
52 static const size_t maximumDomainNameLength = 255;
53 static const int32_t idnaConversionOption = UIDNA_ALLOW_UNASSIGNED;
55 static String convertEmailAddressToASCII(const String& address)
57 if (address.containsOnlyASCII())
60 size_t atPosition = address.find('@');
61 if (atPosition == kNotFound)
64 UErrorCode error = U_ZERO_ERROR;
65 UChar domainNameBuffer[maximumDomainNameLength];
66 int32_t domainNameLength = uidna_IDNToASCII(address.charactersWithNullTermination().data() + atPosition + 1, address.length() - atPosition - 1, domainNameBuffer, WTF_ARRAY_LENGTH(domainNameBuffer), idnaConversionOption, 0, &error);
67 if (error != U_ZERO_ERROR || domainNameLength <= 0)
70 StringBuilder builder;
71 builder.append(address, 0, atPosition + 1);
72 builder.append(domainNameBuffer, domainNameLength);
73 return builder.toString();
76 String EmailInputType::convertEmailAddressToUnicode(const String& address) const
78 if (!address.containsOnlyASCII())
81 size_t atPosition = address.find('@');
82 if (atPosition == kNotFound)
85 if (address.find("xn--", atPosition + 1) == kNotFound)
91 String languages = chrome()->client().acceptLanguages();
92 String unicodeHost = blink::Platform::current()->convertIDNToUnicode(address.substring(atPosition + 1), languages);
93 StringBuilder builder;
94 builder.append(address, 0, atPosition + 1);
95 builder.append(unicodeHost);
96 return builder.toString();
99 static bool isInvalidLocalPartCharacter(UChar ch)
103 DEFINE_STATIC_LOCAL(const String, validCharacters, (localPartCharacters));
104 return validCharacters.find(toASCIILower(ch)) == kNotFound;
107 static bool isInvalidDomainCharacter(UChar ch)
111 return !isASCIILower(ch) && !isASCIIUpper(ch) && !isASCIIDigit(ch) && ch != '.' && ch != '-';
114 static bool checkValidDotUsage(const String& domain)
116 if (domain.isEmpty())
118 if (domain[0] == '.' || domain[domain.length() - 1] == '.')
120 return domain.find("..") == kNotFound;
123 static bool isValidEmailAddress(const String& address)
125 int addressLength = address.length();
129 DEFINE_STATIC_LOCAL(const ScriptRegexp, regExp, (emailPattern, TextCaseInsensitive));
132 int matchOffset = regExp.match(address, 0, &matchLength);
134 return !matchOffset && matchLength == addressLength;
137 PassRefPtr<InputType> EmailInputType::create(HTMLInputElement& element)
139 return adoptRef(new EmailInputType(element));
142 void EmailInputType::countUsage()
144 countUsageIfVisible(UseCounter::InputTypeEmail);
145 bool hasMaxLength = element().fastHasAttribute(HTMLNames::maxlengthAttr);
147 countUsageIfVisible(UseCounter::InputTypeEmailMaxLength);
148 if (element().multiple()) {
149 countUsageIfVisible(UseCounter::InputTypeEmailMultiple);
151 countUsageIfVisible(UseCounter::InputTypeEmailMultipleMaxLength);
155 const AtomicString& EmailInputType::formControlType() const
157 return InputTypeNames::email;
160 // The return value is an invalid email address string if the specified string
161 // contains an invalid email address. Otherwise, null string is returned.
162 // If an empty string is returned, it means empty address is specified.
163 // e.g. "foo@example.com,,bar@example.com" for multiple case.
164 String EmailInputType::findInvalidAddress(const String& value) const
168 if (!element().multiple())
169 return isValidEmailAddress(value) ? String() : value;
170 Vector<String> addresses;
171 value.split(',', true, addresses);
172 for (unsigned i = 0; i < addresses.size(); ++i) {
173 String stripped = stripLeadingAndTrailingHTMLSpaces(addresses[i]);
174 if (!isValidEmailAddress(stripped))
180 bool EmailInputType::typeMismatchFor(const String& value) const
182 return !findInvalidAddress(value).isNull();
185 bool EmailInputType::typeMismatch() const
187 return typeMismatchFor(element().value());
190 String EmailInputType::typeMismatchText() const
192 String invalidAddress = findInvalidAddress(element().value());
193 ASSERT(!invalidAddress.isNull());
194 if (invalidAddress.isEmpty())
195 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmpty);
196 String atSign = String("@");
197 size_t atIndex = invalidAddress.find('@');
198 if (atIndex == kNotFound)
199 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailNoAtSign, atSign, invalidAddress);
200 // We check validity against an ASCII value because of difficulty to check
201 // invalid characters. However we should show Unicode value.
202 String unicodeAddress = convertEmailAddressToUnicode(invalidAddress);
203 String localPart = invalidAddress.left(atIndex);
204 String domain = invalidAddress.substring(atIndex + 1);
205 if (localPart.isEmpty())
206 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmptyLocal, atSign, unicodeAddress);
207 if (domain.isEmpty())
208 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmptyDomain, atSign, unicodeAddress);
209 size_t invalidCharIndex = localPart.find(isInvalidLocalPartCharacter);
210 if (invalidCharIndex != kNotFound) {
211 unsigned charLength = U_IS_LEAD(localPart[invalidCharIndex]) ? 2 : 1;
212 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidLocal, atSign, localPart.substring(invalidCharIndex, charLength));
214 invalidCharIndex = domain.find(isInvalidDomainCharacter);
215 if (invalidCharIndex != kNotFound) {
216 unsigned charLength = U_IS_LEAD(domain[invalidCharIndex]) ? 2 : 1;
217 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidDomain, atSign, domain.substring(invalidCharIndex, charLength));
219 if (!checkValidDotUsage(domain)) {
220 size_t atIndexInUnicode = unicodeAddress.find('@');
221 ASSERT(atIndexInUnicode != kNotFound);
222 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidDots, String("."), unicodeAddress.substring(atIndexInUnicode + 1));
224 if (element().multiple())
225 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForMultipleEmail);
226 return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmail);
229 bool EmailInputType::isEmailField() const
234 bool EmailInputType::supportsSelectionAPI() const
239 String EmailInputType::sanitizeValue(const String& proposedValue) const
241 String noLineBreakValue = proposedValue.removeCharacters(isHTMLLineBreak);
242 if (!element().multiple())
243 return stripLeadingAndTrailingHTMLSpaces(noLineBreakValue);
244 Vector<String> addresses;
245 noLineBreakValue.split(',', true, addresses);
246 StringBuilder strippedValue;
247 for (size_t i = 0; i < addresses.size(); ++i) {
249 strippedValue.append(",");
250 strippedValue.append(stripLeadingAndTrailingHTMLSpaces(addresses[i]));
252 return strippedValue.toString();
255 String EmailInputType::convertFromVisibleValue(const String& visibleValue) const
257 String sanitizedValue = sanitizeValue(visibleValue);
258 if (!element().multiple())
259 return convertEmailAddressToASCII(sanitizedValue);
260 Vector<String> addresses;
261 sanitizedValue.split(',', true, addresses);
262 StringBuilder builder;
263 builder.reserveCapacity(sanitizedValue.length());
264 for (size_t i = 0; i < addresses.size(); ++i) {
267 builder.append(convertEmailAddressToASCII(addresses[i]));
269 return builder.toString();
272 String EmailInputType::visibleValue() const
274 String value = element().value();
275 if (!element().multiple())
276 return convertEmailAddressToUnicode(value);
278 Vector<String> addresses;
279 value.split(',', true, addresses);
280 StringBuilder builder;
281 builder.reserveCapacity(value.length());
282 for (size_t i = 0; i < addresses.size(); ++i) {
285 builder.append(convertEmailAddressToUnicode(addresses[i]));
287 return builder.toString();
290 } // namespace WebCore