Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / forms / EmailInputType.cpp
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 "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>
38
39 namespace WebCore {
40
41 using blink::WebLocalizedString;
42
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
47     "@"
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])?)*";
50
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;
54
55 static String convertEmailAddressToASCII(const String& address)
56 {
57     if (address.containsOnlyASCII())
58         return address;
59
60     size_t atPosition = address.find('@');
61     if (atPosition == kNotFound)
62         return address;
63
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)
68         return address;
69
70     StringBuilder builder;
71     builder.append(address, 0, atPosition + 1);
72     builder.append(domainNameBuffer, domainNameLength);
73     return builder.toString();
74 }
75
76 String EmailInputType::convertEmailAddressToUnicode(const String& address) const
77 {
78     if (!address.containsOnlyASCII())
79         return address;
80
81     size_t atPosition = address.find('@');
82     if (atPosition == kNotFound)
83         return address;
84
85     if (address.find("xn--", atPosition + 1) == kNotFound)
86         return address;
87
88     if (!chrome())
89         return address;
90
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();
97 }
98
99 static bool isInvalidLocalPartCharacter(UChar ch)
100 {
101     if (!isASCII(ch))
102         return true;
103     DEFINE_STATIC_LOCAL(const String, validCharacters, (localPartCharacters));
104     return validCharacters.find(toASCIILower(ch)) == kNotFound;
105 }
106
107 static bool isInvalidDomainCharacter(UChar ch)
108 {
109     if (!isASCII(ch))
110         return true;
111     return !isASCIILower(ch) && !isASCIIUpper(ch) && !isASCIIDigit(ch) && ch != '.' && ch != '-';
112 }
113
114 static bool checkValidDotUsage(const String& domain)
115 {
116     if (domain.isEmpty())
117         return true;
118     if (domain[0] == '.' || domain[domain.length() - 1] == '.')
119         return false;
120     return domain.find("..") == kNotFound;
121 }
122
123 static bool isValidEmailAddress(const String& address)
124 {
125     int addressLength = address.length();
126     if (!addressLength)
127         return false;
128
129     DEFINE_STATIC_LOCAL(const ScriptRegexp, regExp, (emailPattern, TextCaseInsensitive));
130
131     int matchLength;
132     int matchOffset = regExp.match(address, 0, &matchLength);
133
134     return !matchOffset && matchLength == addressLength;
135 }
136
137 PassRefPtr<InputType> EmailInputType::create(HTMLInputElement& element)
138 {
139     return adoptRef(new EmailInputType(element));
140 }
141
142 void EmailInputType::countUsage()
143 {
144     countUsageIfVisible(UseCounter::InputTypeEmail);
145     bool hasMaxLength = element().fastHasAttribute(HTMLNames::maxlengthAttr);
146     if (hasMaxLength)
147         countUsageIfVisible(UseCounter::InputTypeEmailMaxLength);
148     if (element().multiple()) {
149         countUsageIfVisible(UseCounter::InputTypeEmailMultiple);
150         if (hasMaxLength)
151             countUsageIfVisible(UseCounter::InputTypeEmailMultipleMaxLength);
152     }
153 }
154
155 const AtomicString& EmailInputType::formControlType() const
156 {
157     return InputTypeNames::email;
158 }
159
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
165 {
166     if (value.isEmpty())
167         return String();
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))
175             return stripped;
176     }
177     return String();
178 }
179
180 bool EmailInputType::typeMismatchFor(const String& value) const
181 {
182     return !findInvalidAddress(value).isNull();
183 }
184
185 bool EmailInputType::typeMismatch() const
186 {
187     return typeMismatchFor(element().value());
188 }
189
190 String EmailInputType::typeMismatchText() const
191 {
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));
213     }
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));
218     }
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));
223     }
224     if (element().multiple())
225         return locale().queryString(WebLocalizedString::ValidationTypeMismatchForMultipleEmail);
226     return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmail);
227 }
228
229 bool EmailInputType::isEmailField() const
230 {
231     return true;
232 }
233
234 bool EmailInputType::supportsSelectionAPI() const
235 {
236     return false;
237 }
238
239 String EmailInputType::sanitizeValue(const String& proposedValue) const
240 {
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) {
248         if (i > 0)
249             strippedValue.append(",");
250         strippedValue.append(stripLeadingAndTrailingHTMLSpaces(addresses[i]));
251     }
252     return strippedValue.toString();
253 }
254
255 String EmailInputType::convertFromVisibleValue(const String& visibleValue) const
256 {
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) {
265         if (i > 0)
266             builder.append(",");
267         builder.append(convertEmailAddressToASCII(addresses[i]));
268     }
269     return builder.toString();
270 }
271
272 String EmailInputType::visibleValue() const
273 {
274     String value = element().value();
275     if (!element().multiple())
276         return convertEmailAddressToUnicode(value);
277
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) {
283         if (i > 0)
284             builder.append(",");
285         builder.append(convertEmailAddressToUnicode(addresses[i]));
286     }
287     return builder.toString();
288 }
289
290 } // namespace WebCore