SingleLineTextInputTypes.cpp (8225B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/SingleLineTextInputTypes.h" 8 9 #include "HTMLSplitOnSpacesTokenizer.h" 10 #include "mozilla/TextUtils.h" 11 #include "mozilla/dom/BindingDeclarations.h" 12 #include "mozilla/dom/HTMLInputElement.h" 13 #include "nsCRTGlue.h" 14 #include "nsContentUtils.h" 15 #include "nsIIOService.h" 16 #include "nsNetCID.h" 17 #include "nsNetUtil.h" 18 19 using namespace mozilla; 20 using namespace mozilla::dom; 21 22 bool SingleLineTextInputTypeBase::IsMutable() const { 23 return !mInputElement->IsDisabledOrReadOnly(); 24 } 25 26 bool SingleLineTextInputTypeBase::IsTooLong() const { 27 int32_t maxLength = mInputElement->MaxLength(); 28 29 // Maxlength of -1 means attribute isn't set or parsing error. 30 if (maxLength == -1) { 31 return false; 32 } 33 34 int32_t textLength = mInputElement->InputTextLength(CallerType::System); 35 36 return textLength > maxLength; 37 } 38 39 bool SingleLineTextInputTypeBase::IsTooShort() const { 40 int32_t minLength = mInputElement->MinLength(); 41 42 // Minlength of -1 means attribute isn't set or parsing error. 43 if (minLength == -1) { 44 return false; 45 } 46 47 int32_t textLength = mInputElement->InputTextLength(CallerType::System); 48 49 return textLength && textLength < minLength; 50 } 51 52 bool SingleLineTextInputTypeBase::IsValueMissing() const { 53 if (!mInputElement->IsRequired()) { 54 return false; 55 } 56 57 if (!IsMutable()) { 58 return false; 59 } 60 61 return IsValueEmpty(); 62 } 63 64 Maybe<bool> SingleLineTextInputTypeBase::HasPatternMismatch() const { 65 if (!mInputElement->HasPatternAttribute()) { 66 return Some(false); 67 } 68 69 nsAutoString pattern; 70 if (!mInputElement->GetAttr(nsGkAtoms::pattern, pattern)) { 71 return Some(false); 72 } 73 74 nsAutoString value; 75 GetNonFileValueInternal(value); 76 77 if (value.IsEmpty()) { 78 return Some(false); 79 } 80 81 Document* doc = mInputElement->OwnerDoc(); 82 Maybe<bool> result = nsContentUtils::IsPatternMatching( 83 value, std::move(pattern), doc, 84 mInputElement->HasAttr(nsGkAtoms::multiple)); 85 return result ? Some(!*result) : Nothing(); 86 } 87 88 /* input type=url */ 89 90 bool URLInputType::HasTypeMismatch() const { 91 nsAutoString value; 92 GetNonFileValueInternal(value); 93 94 if (value.IsEmpty()) { 95 return false; 96 } 97 98 /** 99 * TODO: 100 * The URL is not checked as the HTML5 specifications want it to be because 101 * there is no code to check for a valid URI/IRI according to 3986 and 3987 102 * RFC's at the moment, see bug 561586. 103 * 104 * RFC 3987 (IRI) implementation: bug 42899 105 * 106 * HTML5 specifications: 107 * http://dev.w3.org/html5/spec/infrastructure.html#valid-url 108 */ 109 nsCOMPtr<nsIIOService> ioService = do_GetIOService(); 110 nsCOMPtr<nsIURI> uri; 111 112 return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr, 113 nullptr, getter_AddRefs(uri))); 114 } 115 116 nsresult URLInputType::GetTypeMismatchMessage(nsAString& aMessage) { 117 return nsContentUtils::GetMaybeLocalizedString( 118 nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidURL", 119 mInputElement->OwnerDoc(), aMessage); 120 } 121 122 /* input type=email */ 123 124 bool EmailInputType::HasTypeMismatch() const { 125 nsAutoString value; 126 GetNonFileValueInternal(value); 127 128 if (value.IsEmpty()) { 129 return false; 130 } 131 132 return mInputElement->HasAttr(nsGkAtoms::multiple) 133 ? !IsValidEmailAddressList(value) 134 : !IsValidEmailAddress(value); 135 } 136 137 bool EmailInputType::HasBadInput() const { 138 // With regards to suffering from bad input the spec says that only the 139 // punycode conversion works, so we don't care whether the email address is 140 // valid or not here. (If the email address is invalid then we will be 141 // suffering from a type mismatch.) 142 nsAutoString value; 143 nsAutoCString unused; 144 uint32_t unused2; 145 GetNonFileValueInternal(value); 146 HTMLSplitOnSpacesTokenizer tokenizer(value, ','); 147 while (tokenizer.hasMoreTokens()) { 148 if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) { 149 return true; 150 } 151 } 152 return false; 153 } 154 155 nsresult EmailInputType::GetTypeMismatchMessage(nsAString& aMessage) { 156 return nsContentUtils::GetMaybeLocalizedString( 157 nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidEmail", 158 mInputElement->OwnerDoc(), aMessage); 159 } 160 161 nsresult EmailInputType::GetBadInputMessage(nsAString& aMessage) { 162 return nsContentUtils::GetMaybeLocalizedString( 163 nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidEmail", 164 mInputElement->OwnerDoc(), aMessage); 165 } 166 167 /* static */ 168 bool EmailInputType::IsValidEmailAddressList(const nsAString& aValue) { 169 HTMLSplitOnSpacesTokenizer tokenizer(aValue, ','); 170 171 while (tokenizer.hasMoreTokens()) { 172 if (!IsValidEmailAddress(tokenizer.nextToken())) { 173 return false; 174 } 175 } 176 177 return !tokenizer.separatorAfterCurrentToken(); 178 } 179 180 /* static */ 181 bool EmailInputType::IsValidEmailAddress(const nsAString& aValue) { 182 nsAutoString trimmed(aValue); 183 trimmed.Trim(" \n\r\t\f"); 184 185 // Email addresses can't be empty and can't end with a '.' or '-'. 186 if (trimmed.IsEmpty() || trimmed.Last() == '.' || trimmed.Last() == '-') { 187 return false; 188 } 189 190 uint32_t atPos; 191 nsAutoCString value; 192 if (!PunycodeEncodeEmailAddress(trimmed, value, &atPos) || 193 atPos == (uint32_t)kNotFound || atPos == 0 || 194 atPos == value.Length() - 1) { 195 // Could not encode, or "@" was not found, or it was at the start or end 196 // of the input - in all cases, not a valid email address. 197 return false; 198 } 199 200 uint32_t length = value.Length(); 201 uint32_t i = 0; 202 203 // Parsing the username. 204 for (; i < atPos; ++i) { 205 char16_t c = value[i]; 206 207 // The username characters have to be in this list to be valid. 208 if (!(IsAsciiAlpha(c) || IsAsciiDigit(c) || c == '.' || c == '!' || 209 c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || 210 c == '*' || c == '+' || c == '-' || c == '/' || c == '=' || 211 c == '?' || c == '^' || c == '_' || c == '`' || c == '{' || 212 c == '|' || c == '}' || c == '~')) { 213 return false; 214 } 215 } 216 217 // Skip the '@'. 218 ++i; 219 220 // The domain name can't begin with a dot or a dash. 221 if (value[i] == '.' || value[i] == '-') { 222 return false; 223 } 224 225 // Parsing the domain name. 226 for (; i < length; ++i) { 227 char16_t c = value[i]; 228 229 if (c == '.') { 230 // A dot can't follow a dot or a dash. 231 if (value[i - 1] == '.' || value[i - 1] == '-') { 232 return false; 233 } 234 } else if (c == '-') { 235 // A dash can't follow a dot. 236 if (value[i - 1] == '.') { 237 return false; 238 } 239 } else if (!(IsAsciiAlpha(c) || IsAsciiDigit(c) || c == '-')) { 240 // The domain characters have to be in this list to be valid. 241 return false; 242 } 243 } 244 245 return true; 246 } 247 248 /* static */ 249 bool EmailInputType::PunycodeEncodeEmailAddress(const nsAString& aEmail, 250 nsAutoCString& aEncodedEmail, 251 uint32_t* aIndexOfAt) { 252 nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail); 253 *aIndexOfAt = (uint32_t)value.FindChar('@'); 254 255 if (*aIndexOfAt == (uint32_t)kNotFound || *aIndexOfAt == value.Length() - 1) { 256 aEncodedEmail = value; 257 return true; 258 } 259 260 uint32_t indexOfDomain = *aIndexOfAt + 1; 261 262 const nsDependentCSubstring domain = Substring(value, indexOfDomain); 263 nsAutoCString domainACE; 264 NS_DomainToASCII(domain, domainACE); 265 266 // NS_DomainToASCII does not check length (removed in bug 1788115), so we 267 // check for that limit here as required by the spec: 268 // https://html.spec.whatwg.org/#valid-e-mail-address 269 nsCCharSeparatedTokenizer tokenizer(domainACE, '.'); 270 while (tokenizer.hasMoreTokens()) { 271 // XXX should check each token for not starting or ending with hyphen; 272 // https://bugzilla.mozilla.org/show_bug.cgi?id=1890466 273 if (tokenizer.nextToken().Length() > 63) { 274 return false; 275 } 276 } 277 278 value.Replace(indexOfDomain, domain.Length(), domainACE); 279 280 aEncodedEmail = value; 281 return true; 282 }