blob: 20db60bf5f2b96adae3fb789b2b0c53a8d8f8fd0 [file] [log] [blame]
/*
* This file is part of the WebKit project.
*
* Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com>
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "core/html/forms/EmailInputType.h"
#include "InputTypeNames.h"
#include "bindings/v8/ScriptRegexp.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/page/Chrome.h"
#include "core/page/ChromeClient.h"
#include "platform/text/PlatformLocale.h"
#include "public/platform/Platform.h"
#include "wtf/PassOwnPtr.h"
#include "wtf/text/StringBuilder.h"
#include <unicode/uidna.h>
namespace WebCore {
using blink::WebLocalizedString;
// http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
static const char localPartCharacters[] = "abcdefghijklmnopqrstuvwxyz0123456789!#$%&'*+/=?^_`{|}~.-";
static const char emailPattern[] =
"[a-z0-9!#$%&'*+/=?^_`{|}~.-]+" // local part
"@"
"[a-z0-9-]+(\\.[a-z0-9-]+)*"; // domain part
// RFC5321 says the maximum total length of a domain name is 255 octets.
static const size_t maximumDomainNameLength = 255;
static const int32_t idnaConversionOption = UIDNA_ALLOW_UNASSIGNED;
static String convertEmailAddressToASCII(const String& address)
{
if (address.containsOnlyASCII())
return address;
size_t atPosition = address.find('@');
if (atPosition == kNotFound)
return address;
UErrorCode error = U_ZERO_ERROR;
UChar domainNameBuffer[maximumDomainNameLength];
int32_t domainNameLength = uidna_IDNToASCII(address.charactersWithNullTermination().data() + atPosition + 1, address.length() - atPosition - 1, domainNameBuffer, WTF_ARRAY_LENGTH(domainNameBuffer), idnaConversionOption, 0, &error);
if (error != U_ZERO_ERROR || domainNameLength <= 0)
return address;
StringBuilder builder;
builder.append(address, 0, atPosition + 1);
builder.append(domainNameBuffer, domainNameLength);
return builder.toString();
}
String EmailInputType::convertEmailAddressToUnicode(const String& address) const
{
if (!address.containsOnlyASCII())
return address;
size_t atPosition = address.find('@');
if (atPosition == kNotFound)
return address;
if (address.find("xn--", atPosition + 1) == kNotFound)
return address;
if (!chrome())
return address;
String languages = chrome()->client().acceptLanguages();
String unicodeHost = blink::Platform::current()->convertIDNToUnicode(address.substring(atPosition + 1), languages);
StringBuilder builder;
builder.append(address, 0, atPosition + 1);
builder.append(unicodeHost);
return builder.toString();
}
static bool isInvalidLocalPartCharacter(UChar ch)
{
if (!isASCII(ch))
return true;
DEFINE_STATIC_LOCAL(const String, validCharacters, (localPartCharacters));
return validCharacters.find(toASCIILower(ch)) == kNotFound;
}
static bool isInvalidDomainCharacter(UChar ch)
{
if (!isASCII(ch))
return true;
return !isASCIILower(ch) && !isASCIIUpper(ch) && !isASCIIDigit(ch) && ch != '.' && ch != '-';
}
static bool checkValidDotUsage(const String& domain)
{
if (domain.isEmpty())
return true;
if (domain[0] == '.' || domain[domain.length() - 1] == '.')
return false;
return domain.find("..") == kNotFound;
}
static bool isValidEmailAddress(const String& address)
{
int addressLength = address.length();
if (!addressLength)
return false;
DEFINE_STATIC_LOCAL(const ScriptRegexp, regExp, (emailPattern, TextCaseInsensitive));
int matchLength;
int matchOffset = regExp.match(address, 0, &matchLength);
return !matchOffset && matchLength == addressLength;
}
PassRefPtr<InputType> EmailInputType::create(HTMLInputElement& element)
{
return adoptRef(new EmailInputType(element));
}
void EmailInputType::countUsage()
{
countUsageIfVisible(UseCounter::InputTypeEmail);
bool hasMaxLength = element().fastHasAttribute(HTMLNames::maxlengthAttr);
if (hasMaxLength)
countUsageIfVisible(UseCounter::InputTypeEmailMaxLength);
if (element().multiple()) {
countUsageIfVisible(UseCounter::InputTypeEmailMultiple);
if (hasMaxLength)
countUsageIfVisible(UseCounter::InputTypeEmailMultipleMaxLength);
}
}
const AtomicString& EmailInputType::formControlType() const
{
return InputTypeNames::email;
}
// The return value is an invalid email address string if the specified string
// contains an invalid email address. Otherwise, null string is returned.
// If an empty string is returned, it means empty address is specified.
// e.g. "foo@example.com,,bar@example.com" for multiple case.
String EmailInputType::findInvalidAddress(const String& value) const
{
if (value.isEmpty())
return String();
if (!element().multiple())
return isValidEmailAddress(value) ? String() : value;
Vector<String> addresses;
value.split(',', true, addresses);
for (unsigned i = 0; i < addresses.size(); ++i) {
String stripped = stripLeadingAndTrailingHTMLSpaces(addresses[i]);
if (!isValidEmailAddress(stripped))
return stripped;
}
return String();
}
bool EmailInputType::typeMismatchFor(const String& value) const
{
return !findInvalidAddress(value).isNull();
}
bool EmailInputType::typeMismatch() const
{
return typeMismatchFor(element().value());
}
String EmailInputType::typeMismatchText() const
{
String invalidAddress = findInvalidAddress(element().value());
ASSERT(!invalidAddress.isNull());
if (invalidAddress.isEmpty())
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmpty);
String atSign = String("@");
size_t atIndex = invalidAddress.find('@');
if (atIndex == kNotFound)
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailNoAtSign, atSign, invalidAddress);
// We check validity against an ASCII value because of difficulty to check
// invalid characters. However we should show Unicode value.
String unicodeAddress = convertEmailAddressToUnicode(invalidAddress);
String localPart = invalidAddress.left(atIndex);
String domain = invalidAddress.substring(atIndex + 1);
if (localPart.isEmpty())
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmptyLocal, atSign, unicodeAddress);
if (domain.isEmpty())
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailEmptyDomain, atSign, unicodeAddress);
size_t invalidCharIndex = localPart.find(isInvalidLocalPartCharacter);
if (invalidCharIndex != kNotFound) {
unsigned charLength = U_IS_LEAD(localPart[invalidCharIndex]) ? 2 : 1;
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidLocal, atSign, localPart.substring(invalidCharIndex, charLength));
}
invalidCharIndex = domain.find(isInvalidDomainCharacter);
if (invalidCharIndex != kNotFound) {
unsigned charLength = U_IS_LEAD(domain[invalidCharIndex]) ? 2 : 1;
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidDomain, atSign, domain.substring(invalidCharIndex, charLength));
}
if (!checkValidDotUsage(domain)) {
size_t atIndexInUnicode = unicodeAddress.find('@');
ASSERT(atIndexInUnicode != kNotFound);
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmailInvalidDots, String("."), unicodeAddress.substring(atIndexInUnicode + 1));
}
if (element().multiple())
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForMultipleEmail);
return locale().queryString(WebLocalizedString::ValidationTypeMismatchForEmail);
}
bool EmailInputType::isEmailField() const
{
return true;
}
bool EmailInputType::supportsSelectionAPI() const
{
return false;
}
String EmailInputType::sanitizeValue(const String& proposedValue) const
{
String noLineBreakValue = proposedValue.removeCharacters(isHTMLLineBreak);
if (!element().multiple())
return stripLeadingAndTrailingHTMLSpaces(noLineBreakValue);
Vector<String> addresses;
noLineBreakValue.split(',', true, addresses);
StringBuilder strippedValue;
for (size_t i = 0; i < addresses.size(); ++i) {
if (i > 0)
strippedValue.append(",");
strippedValue.append(stripLeadingAndTrailingHTMLSpaces(addresses[i]));
}
return strippedValue.toString();
}
String EmailInputType::convertFromVisibleValue(const String& visibleValue) const
{
String sanitizedValue = sanitizeValue(visibleValue);
if (!element().multiple())
return convertEmailAddressToASCII(sanitizedValue);
Vector<String> addresses;
sanitizedValue.split(',', true, addresses);
StringBuilder builder;
builder.reserveCapacity(sanitizedValue.length());
for (size_t i = 0; i < addresses.size(); ++i) {
if (i > 0)
builder.append(",");
builder.append(convertEmailAddressToASCII(addresses[i]));
}
return builder.toString();
}
String EmailInputType::visibleValue() const
{
String value = element().value();
if (!element().multiple())
return convertEmailAddressToUnicode(value);
Vector<String> addresses;
value.split(',', true, addresses);
StringBuilder builder;
builder.reserveCapacity(value.length());
for (size_t i = 0; i < addresses.size(); ++i) {
if (i > 0)
builder.append(",");
builder.append(convertEmailAddressToUnicode(addresses[i]));
}
return builder.toString();
}
} // namespace WebCore