Bug 1345767 - Part 4: Factor HasTypeMismatch() out of HTMLInputElement. r?smaug draft
authorJessica Jong <jjong@mozilla.com>
Thu, 04 May 2017 15:08:24 +0800
changeset 572490 8af7c637475972b25b28a3277c39f3de16b6aa9b
parent 572489 9c9cdf3c7a18592ec1fd918100dadf23c3ddbaa6
child 572491 9aae21fad5340d0f24295c07e02afb19deac4ce3
push id57094
push userjjong@mozilla.com
push dateThu, 04 May 2017 07:44:57 +0000
reviewerssmaug
bugs1345767
milestone55.0a1
Bug 1345767 - Part 4: Factor HasTypeMismatch() out of HTMLInputElement. r?smaug MozReview-Commit-ID: 7kW0Ojnp2OE
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/input/InputType.cpp
dom/html/input/InputType.h
dom/html/input/SingleLineTextInputTypes.cpp
dom/html/input/SingleLineTextInputTypes.h
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -46,17 +46,16 @@
 #include "nsIPresShell.h"
 #include "nsIFormControlFrame.h"
 #include "nsITextControlFrame.h"
 #include "nsIFrame.h"
 #include "nsRangeFrame.h"
 #include "nsIServiceManager.h"
 #include "nsError.h"
 #include "nsIEditor.h"
-#include "nsIIOService.h"
 #include "nsDocument.h"
 #include "nsAttrValueOrString.h"
 #include "nsDateTimeControlFrame.h"
 
 #include "nsPresState.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMNodeList.h"
 #include "nsIDOMHTMLCollection.h"
@@ -81,18 +80,16 @@
 #include "nsIRadioGroupContainer.h"
 
 // input type=file
 #include "mozilla/dom/FileSystemEntry.h"
 #include "mozilla/dom/FileSystem.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileList.h"
 #include "nsIFile.h"
-#include "nsNetCID.h"
-#include "nsNetUtil.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIContentPrefService.h"
 #include "nsIMIMEService.h"
 #include "nsIObserverService.h"
 #include "nsIPopupWindowManager.h"
 #include "nsGlobalWindow.h"
 
 // input type=image
@@ -7524,50 +7521,17 @@ HTMLInputElement::IsValueMissing() const
   MOZ_ASSERT(mType != NS_FORM_INPUT_RADIO);
 
   return mInputType->IsValueMissing();
 }
 
 bool
 HTMLInputElement::HasTypeMismatch() const
 {
-  if (mType != NS_FORM_INPUT_EMAIL && mType != NS_FORM_INPUT_URL) {
-    return false;
-  }
-
-  nsAutoString value;
-  GetNonFileValueInternal(value);
-
-  if (value.IsEmpty()) {
-    return false;
-  }
-
-  if (mType == NS_FORM_INPUT_EMAIL) {
-    return HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)
-             ? !IsValidEmailAddressList(value) : !IsValidEmailAddress(value);
-  } else if (mType == NS_FORM_INPUT_URL) {
-    /**
-     * TODO:
-     * The URL is not checked as the HTML5 specifications want it to be because
-     * there is no code to check for a valid URI/IRI according to 3986 and 3987
-     * RFC's at the moment, see bug 561586.
-     *
-     * RFC 3987 (IRI) implementation: bug 42899
-     *
-     * HTML5 specifications:
-     * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
-     */
-    nsCOMPtr<nsIIOService> ioService = do_GetIOService();
-    nsCOMPtr<nsIURI> uri;
-
-    return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
-                                           nullptr, getter_AddRefs(uri)));
-  }
-
-  return false;
+  return mInputType->HasTypeMismatch();
 }
 
 bool
 HTMLInputElement::HasPatternMismatch() const
 {
   if (!DoesPatternApply() ||
       !HasAttr(kNameSpaceID_None, nsGkAtoms::pattern)) {
     return false;
@@ -8133,98 +8097,16 @@ HTMLInputElement::GetValidationMessage(n
     }
     default:
       rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
   }
 
   return rv;
 }
 
-//static
-bool
-HTMLInputElement::IsValidEmailAddressList(const nsAString& aValue)
-{
-  HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
-
-  while (tokenizer.hasMoreTokens()) {
-    if (!IsValidEmailAddress(tokenizer.nextToken())) {
-      return false;
-    }
-  }
-
-  return !tokenizer.separatorAfterCurrentToken();
-}
-
-//static
-bool
-HTMLInputElement::IsValidEmailAddress(const nsAString& aValue)
-{
-  // Email addresses can't be empty and can't end with a '.' or '-'.
-  if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
-    return false;
-  }
-
-  uint32_t atPos;
-  nsAutoCString value;
-  if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
-      atPos == (uint32_t)kNotFound || atPos == 0 || atPos == value.Length() - 1) {
-    // Could not encode, or "@" was not found, or it was at the start or end
-    // of the input - in all cases, not a valid email address.
-    return false;
-  }
-
-  uint32_t length = value.Length();
-  uint32_t i = 0;
-
-  // Parsing the username.
-  for (; i < atPos; ++i) {
-    char16_t c = value[i];
-
-    // The username characters have to be in this list to be valid.
-    if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
-          c == '.' || c == '!' || c == '#' || c == '$' || c == '%' ||
-          c == '&' || c == '\''|| c == '*' || c == '+' || c == '-' ||
-          c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
-          c == '`' || c == '{' || c == '|' || c == '}' || c == '~' )) {
-      return false;
-    }
-  }
-
-  // Skip the '@'.
-  ++i;
-
-  // The domain name can't begin with a dot or a dash.
-  if (value[i] == '.' || value[i] == '-') {
-    return false;
-  }
-
-  // Parsing the domain name.
-  for (; i < length; ++i) {
-    char16_t c = value[i];
-
-    if (c == '.') {
-      // A dot can't follow a dot or a dash.
-      if (value[i-1] == '.' || value[i-1] == '-') {
-        return false;
-      }
-    } else if (c == '-'){
-      // A dash can't follow a dot.
-      if (value[i-1] == '.') {
-        return false;
-      }
-    } else if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
-                 c == '-')) {
-      // The domain characters have to be in this list to be valid.
-      return false;
-    }
-  }
-
-  return true;
-}
-
 NS_IMETHODIMP_(bool)
 HTMLInputElement::IsSingleLineTextControl() const
 {
   return IsSingleLineTextControl(false);
 }
 
 NS_IMETHODIMP_(bool)
 HTMLInputElement::IsTextArea() const
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -920,38 +920,16 @@ protected:
     // On getting, returns "C:\fakepath\" followed by the file name of the
     // first file of the selected files if any.
     // On setting the empty string, empties the selected files list, otherwise
     // throw the INVALID_STATE_ERR exception.
     VALUE_MODE_FILENAME
   };
 
   /**
-   * This helper method returns true if aValue is a valid email address.
-   * This is following the HTML5 specification:
-   * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address
-   *
-   * @param aValue  the email address to check.
-   * @result        whether the given string is a valid email address.
-   */
-  static bool IsValidEmailAddress(const nsAString& aValue);
-
-  /**
-   * This helper method returns true if aValue is a valid email address list.
-   * Email address list is a list of email address separated by comas (,) which
-   * can be surrounded by space charecters.
-   * This is following the HTML5 specification:
-   * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address-list
-   *
-   * @param aValue  the email address list to check.
-   * @result        whether the given string is a valid email address list.
-   */
-  static bool IsValidEmailAddressList(const nsAString& aValue);
-
-  /**
    * This helper method convert a sub-string that contains only digits to a
    * number (unsigned int given that it can't contain a minus sign).
    * This method will return whether the sub-string is correctly formatted
    * (ie. contains only digit) and it can be successfuly parsed to generate a
    * number).
    * If the method returns true, |aResult| will contained the parsed number.
    *
    * @param aValue  the string on which the sub-string will be extracted and parsed.
--- a/dom/html/input/InputType.cpp
+++ b/dom/html/input/InputType.cpp
@@ -110,16 +110,22 @@ InputType::IsMutable() const
 
 bool
 InputType::IsValueEmpty() const
 {
   return mInputElement->IsValueEmpty();
 }
 
 void
+InputType::GetNonFileValueInternal(nsAString& aValue) const
+{
+  return mInputElement->GetNonFileValueInternal(aValue);
+}
+
+void
 InputType::DropReference()
 {
   // Drop our (non ref-counted) reference.
   mInputElement = nullptr;
 }
 
 bool
 InputType::IsTooLong() const
@@ -133,8 +139,14 @@ InputType::IsTooShort() const
   return false;
 }
 
 bool
 InputType::IsValueMissing() const
 {
   return false;
 }
+
+bool
+InputType::HasTypeMismatch() const
+{
+  return false;
+}
--- a/dom/html/input/InputType.h
+++ b/dom/html/input/InputType.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef InputType_h__
 #define InputType_h__
 
 #include <stdint.h>
 #include "mozilla/UniquePtr.h"
+#include "nsString.h"
 
 namespace mozilla {
 namespace dom {
 class HTMLInputElement;
 } // namespace dom
 } // namespace mozilla
 
 struct DoNotDelete;
@@ -33,16 +34,17 @@ public:
   /**
    * Drop the reference to the input element.
    */
   void DropReference();
 
   virtual bool IsTooLong() const;
   virtual bool IsTooShort() const;
   virtual bool IsValueMissing() const;
+  virtual bool HasTypeMismatch() const;
 
 protected:
   explicit InputType(mozilla::dom::HTMLInputElement* aInputElement)
     : mInputElement(aInputElement)
   {}
 
   /**
    * Get the mutable state of the element.
@@ -57,16 +59,20 @@ protected:
    * Returns whether the input element's current value is the empty string.
    * This only makes sense for some input types; does NOT make sense for file
    * inputs.
    *
    * @return whether the input element's current value is the empty string.
    */
   bool IsValueEmpty() const;
 
+  // A getter for callers that know we're not dealing with a file input, so they
+  // don't have to think about the caller type.
+  void GetNonFileValueInternal(nsAString& aValue) const;
+
   mozilla::dom::HTMLInputElement* mInputElement;
 };
 
 // Custom deleter for UniquePtr<InputType> to avoid freeing memory pre-allocated
 // for InputType, but we still need to call the destructor explictly.
 struct DoNotDelete { void operator()(::InputType* p) { p->~InputType(); } };
 
 #endif /* InputType_h__ */
--- a/dom/html/input/SingleLineTextInputTypes.cpp
+++ b/dom/html/input/SingleLineTextInputTypes.cpp
@@ -3,16 +3,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "SingleLineTextInputTypes.h"
 
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "nsCRTGlue.h"
+#include "nsIIDNService.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
 
 bool
 SingleLineTextInputTypeBase::IsMutable() const
 {
   return !mInputElement->IsDisabled() &&
          !mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
 }
 
@@ -56,8 +62,171 @@ SingleLineTextInputTypeBase::IsValueMiss
   }
 
   if (!IsMutable()) {
     return false;
   }
 
   return IsValueEmpty();
 }
+
+/* input type=url */
+
+bool
+URLInputType::HasTypeMismatch() const
+{
+  nsAutoString value;
+  GetNonFileValueInternal(value);
+
+  if (value.IsEmpty()) {
+    return false;
+  }
+
+  /**
+   * TODO:
+   * The URL is not checked as the HTML5 specifications want it to be because
+   * there is no code to check for a valid URI/IRI according to 3986 and 3987
+   * RFC's at the moment, see bug 561586.
+   *
+   * RFC 3987 (IRI) implementation: bug 42899
+   *
+   * HTML5 specifications:
+   * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
+   */
+  nsCOMPtr<nsIIOService> ioService = do_GetIOService();
+  nsCOMPtr<nsIURI> uri;
+
+  return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
+                                         nullptr, getter_AddRefs(uri)));
+
+}
+
+/* input type=email */
+
+bool
+EmailInputType::HasTypeMismatch() const
+{
+  nsAutoString value;
+  GetNonFileValueInternal(value);
+
+  if (value.IsEmpty()) {
+    return false;
+  }
+
+  return mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
+    !IsValidEmailAddressList(value) : !IsValidEmailAddress(value);
+}
+
+/* static */ bool
+EmailInputType::IsValidEmailAddressList(const nsAString& aValue)
+{
+  HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
+
+  while (tokenizer.hasMoreTokens()) {
+    if (!IsValidEmailAddress(tokenizer.nextToken())) {
+      return false;
+    }
+  }
+
+  return !tokenizer.separatorAfterCurrentToken();
+}
+
+/* static */ bool
+EmailInputType::IsValidEmailAddress(const nsAString& aValue)
+{
+  // Email addresses can't be empty and can't end with a '.' or '-'.
+  if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
+    return false;
+  }
+
+  uint32_t atPos;
+  nsAutoCString value;
+  if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
+      atPos == (uint32_t)kNotFound || atPos == 0 || atPos == value.Length() - 1) {
+    // Could not encode, or "@" was not found, or it was at the start or end
+    // of the input - in all cases, not a valid email address.
+    return false;
+  }
+
+  uint32_t length = value.Length();
+  uint32_t i = 0;
+
+  // Parsing the username.
+  for (; i < atPos; ++i) {
+    char16_t c = value[i];
+
+    // The username characters have to be in this list to be valid.
+    if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
+          c == '.' || c == '!' || c == '#' || c == '$' || c == '%' ||
+          c == '&' || c == '\''|| c == '*' || c == '+' || c == '-' ||
+          c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
+          c == '`' || c == '{' || c == '|' || c == '}' || c == '~' )) {
+      return false;
+    }
+  }
+
+  // Skip the '@'.
+  ++i;
+
+  // The domain name can't begin with a dot or a dash.
+  if (value[i] == '.' || value[i] == '-') {
+    return false;
+  }
+
+  // Parsing the domain name.
+  for (; i < length; ++i) {
+    char16_t c = value[i];
+
+    if (c == '.') {
+      // A dot can't follow a dot or a dash.
+      if (value[i-1] == '.' || value[i-1] == '-') {
+        return false;
+      }
+    } else if (c == '-'){
+      // A dash can't follow a dot.
+      if (value[i-1] == '.') {
+        return false;
+      }
+    } else if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
+                 c == '-')) {
+      // The domain characters have to be in this list to be valid.
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/* static */ bool
+EmailInputType::PunycodeEncodeEmailAddress(const nsAString& aEmail,
+                                           nsAutoCString& aEncodedEmail,
+                                           uint32_t* aIndexOfAt)
+{
+  nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail);
+  *aIndexOfAt = (uint32_t)value.FindChar('@');
+
+  if (*aIndexOfAt == (uint32_t)kNotFound ||
+      *aIndexOfAt == value.Length() - 1) {
+    aEncodedEmail = value;
+    return true;
+  }
+
+  nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+  if (!idnSrv) {
+    NS_ERROR("nsIIDNService isn't present!");
+    return false;
+  }
+
+  uint32_t indexOfDomain = *aIndexOfAt + 1;
+
+  const nsDependentCSubstring domain = Substring(value, indexOfDomain);
+  bool ace;
+  if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) {
+    nsAutoCString domainACE;
+    if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) {
+      return false;
+    }
+    value.Replace(indexOfDomain, domain.Length(), domainACE);
+  }
+
+  aEncodedEmail = value;
+  return true;
+}
--- a/dom/html/input/SingleLineTextInputTypes.h
+++ b/dom/html/input/SingleLineTextInputTypes.h
@@ -80,36 +80,82 @@ class URLInputType : public SingleLineTe
 {
 public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) URLInputType(aInputElement);
   }
 
+  bool HasTypeMismatch() const override;
+
 private:
   explicit URLInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : SingleLineTextInputTypeBase(aInputElement)
   {}
 };
 
 // input type=email
 class EmailInputType : public SingleLineTextInputTypeBase
 {
 public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) EmailInputType(aInputElement);
   }
 
+  bool HasTypeMismatch() const override;
+
 private:
   explicit EmailInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : SingleLineTextInputTypeBase(aInputElement)
   {}
+
+  /**
+   * This helper method returns true if aValue is a valid email address.
+   * This is following the HTML5 specification:
+   * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address
+   *
+   * @param aValue  the email address to check.
+   * @result        whether the given string is a valid email address.
+   */
+  static bool IsValidEmailAddress(const nsAString& aValue);
+
+  /**
+   * This helper method returns true if aValue is a valid email address list.
+   * Email address list is a list of email address separated by comas (,) which
+   * can be surrounded by space charecters.
+   * This is following the HTML5 specification:
+   * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address-list
+   *
+   * @param aValue  the email address list to check.
+   * @result        whether the given string is a valid email address list.
+   */
+  static bool IsValidEmailAddressList(const nsAString& aValue);
+
+  /**
+   * Takes aEmail and attempts to convert everything after the first "@"
+   * character (if anything) to punycode before returning the complete result
+   * via the aEncodedEmail out-param. The aIndexOfAt out-param is set to the
+   * index of the "@" character.
+   *
+   * If no "@" is found in aEmail, aEncodedEmail is simply set to aEmail and
+   * the aIndexOfAt out-param is set to kNotFound.
+   *
+   * Returns true in all cases unless an attempt to punycode encode fails. If
+   * false is returned, aEncodedEmail has not been set.
+   *
+   * This function exists because ConvertUTF8toACE() splits on ".", meaning that
+   * for 'user.name@sld.tld' it would treat "name@sld" as a label. We want to
+   * encode the domain part only.
+   */
+ static bool PunycodeEncodeEmailAddress(const nsAString& aEmail,
+                                        nsAutoCString& aEncodedEmail,
+                                        uint32_t* aIndexOfAt);
 };
 
 // input type=password
 class PasswordInputType : public SingleLineTextInputTypeBase
 {
 public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)