diff --git a/dom/html/HTMLDNSPrefetch.cpp b/dom/html/HTMLDNSPrefetch.cpp
index b9c97b2d2f34..39984d3534fe 100644
--- a/dom/html/HTMLDNSPrefetch.cpp
+++ b/dom/html/HTMLDNSPrefetch.cpp
@@ -229,10 +229,10 @@ nsresult HTMLDNSPrefetch::Prefetch(
const OriginAttributes& aPartitionedPrincipalOriginAttributes,
nsIDNSService::DNSFlags flags) {
if (IsNeckoChild()) {
- // We need to check IsEmpty() because net_IsValidHostName()
+ // We need to check IsEmpty() because net_IsValidDNSHost()
// considers empty strings to be valid hostnames
if (!hostname.IsEmpty() &&
- net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
+ net_IsValidDNSHost(NS_ConvertUTF16toUTF8(hostname))) {
// during shutdown gNeckoChild might be null
if (gNeckoChild) {
gNeckoChild->SendHTMLDNSPrefetch(
@@ -312,10 +312,10 @@ nsresult HTMLDNSPrefetch::CancelPrefetch(
nsIDNSService::DNSFlags flags, nsresult aReason) {
// Forward this request to Necko Parent if we're a child process
if (IsNeckoChild()) {
- // We need to check IsEmpty() because net_IsValidHostName()
+ // We need to check IsEmpty() because net_IsValidDNSHost()
// considers empty strings to be valid hostnames
if (!hostname.IsEmpty() &&
- net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
+ net_IsValidDNSHost(NS_ConvertUTF16toUTF8(hostname))) {
// during shutdown gNeckoChild might be null
if (gNeckoChild && gNeckoChild->CanSend()) {
gNeckoChild->SendCancelHTMLDNSPrefetch(
diff --git a/dom/privateattribution/PrivateAttribution.cpp b/dom/privateattribution/PrivateAttribution.cpp
index cd7244d42bf4..f6872008e653 100644
--- a/dom/privateattribution/PrivateAttribution.cpp
+++ b/dom/privateattribution/PrivateAttribution.cpp
@@ -53,7 +53,7 @@ bool PrivateAttribution::GetSourceHostIfNonPrivate(nsACString& aSourceHost,
[[nodiscard]] static bool ValidateHost(const nsACString& aHost,
ErrorResult& aRv) {
- if (!net_IsValidHostName(aHost)) {
+ if (!net_IsValidDNSHost(aHost)) {
aRv.ThrowSyntaxError(aHost + " is not a valid host name"_ns);
return false;
}
diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp
index 75a45a476068..5ed3abb1cf27 100644
--- a/netwerk/base/Dashboard.cpp
+++ b/netwerk/base/Dashboard.cpp
@@ -1115,7 +1115,7 @@ nsresult Dashboard::TestNewConnection(ConnectionData* aConnectionData) {
nsresult rv;
if (!connectionData->mHost.Length() ||
- !net_IsValidHostName(connectionData->mHost)) {
+ !net_IsValidDNSHost(connectionData->mHost)) {
return NS_ERROR_UNKNOWN_HOST;
}
diff --git a/netwerk/base/IPv4Parser.cpp b/netwerk/base/IPv4Parser.cpp
new file mode 100644
index 000000000000..3a0f472cb68e
--- /dev/null
+++ b/netwerk/base/IPv4Parser.cpp
@@ -0,0 +1,298 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "IPv4Parser.h"
+#include "mozilla/EndianUtils.h"
+#include "nsPrintfCString.h"
+#include "nsTArray.h"
+
+namespace mozilla::net::IPv4Parser {
+
+// https://url.spec.whatwg.org/#ends-in-a-number-checker
+bool EndsInANumber(const nsCString& input) {
+ // 1. Let parts be the result of strictly splitting input on U+002E (.).
+ nsTArray parts;
+ for (const nsDependentCSubstring& part : input.Split('.')) {
+ parts.AppendElement(part);
+ }
+
+ if (parts.Length() == 0) {
+ return false;
+ }
+
+ // 2.If the last item in parts is the empty string, then:
+ // 1. If parts’s size is 1, then return false.
+ // 2. Remove the last item from parts.
+ if (parts.LastElement().IsEmpty()) {
+ if (parts.Length() == 1) {
+ return false;
+ }
+ Unused << parts.PopLastElement();
+ }
+
+ // 3. Let last be the last item in parts.
+ const nsDependentCSubstring& last = parts.LastElement();
+
+ // 4. If last is non-empty and contains only ASCII digits, then return true.
+ // The erroneous input "09" will be caught by the IPv4 parser at a later
+ // stage.
+ if (!last.IsEmpty()) {
+ if (ContainsOnlyAsciiDigits(last)) {
+ return true;
+ }
+ }
+
+ // 5. If parsing last as an IPv4 number does not return failure, then return
+ // true. This is equivalent to checking that last is "0X" or "0x", followed by
+ // zero or more ASCII hex digits.
+ if (StringBeginsWith(last, "0x"_ns) || StringBeginsWith(last, "0X"_ns)) {
+ if (ContainsOnlyAsciiHexDigits(Substring(last, 2))) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
+ uint32_t maxNumber) {
+ uint64_t value = 0;
+ const char* current = input.BeginReading();
+ const char* end = input.EndReading();
+ for (; current < end; ++current) {
+ char c = *current;
+ MOZ_ASSERT(c >= '0' && c <= '9');
+ value *= 10;
+ value += c - '0';
+ }
+ if (value <= maxNumber) {
+ number = value;
+ return NS_OK;
+ }
+
+ // The error case
+ number = 0;
+ return NS_ERROR_FAILURE;
+}
+
+nsresult ParseIPv4Number(const nsACString& input, int32_t base,
+ uint32_t& number, uint32_t maxNumber) {
+ // Accumulate in the 64-bit value
+ uint64_t value = 0;
+ const char* current = input.BeginReading();
+ const char* end = input.EndReading();
+ switch (base) {
+ case 16:
+ ++current;
+ [[fallthrough]];
+ case 8:
+ ++current;
+ break;
+ case 10:
+ default:
+ break;
+ }
+ for (; current < end; ++current) {
+ value *= base;
+ char c = *current;
+ MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) ||
+ (base == 8 && c >= '0' && c <= '7') ||
+ (base == 16 && IsAsciiHexDigit(c)));
+ if (IsAsciiDigit(c)) {
+ value += c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ value += c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ value += c - 'A' + 10;
+ }
+ }
+
+ if (value <= maxNumber) {
+ number = value;
+ return NS_OK;
+ }
+
+ // The error case
+ number = 0;
+ return NS_ERROR_FAILURE;
+}
+
+// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
+nsresult NormalizeIPv4(const nsACString& host, nsCString& result) {
+ int32_t bases[4] = {10, 10, 10, 10};
+ bool onlyBase10 = true; // Track this as a special case
+ int32_t dotIndex[3]; // The positions of the dots in the string
+
+ // Use "length" rather than host.Length() after call to
+ // ValidateIPv4Number because of potential trailing period.
+ nsDependentCSubstring filteredHost;
+ bool trailingDot = false;
+ if (host.Length() > 0 && host.Last() == '.') {
+ trailingDot = true;
+ filteredHost.Rebind(host.BeginReading(), host.Length() - 1);
+ } else {
+ filteredHost.Rebind(host.BeginReading(), host.Length());
+ }
+
+ int32_t length = static_cast(filteredHost.Length());
+ int32_t dotCount = ValidateIPv4Number(filteredHost, bases, dotIndex,
+ onlyBase10, length, trailingDot);
+ if (dotCount < 0 || length <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Max values specified by the spec
+ static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, 0xffffu,
+ 0xffu};
+ uint32_t ipv4;
+ int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0);
+
+ // parse the last part first
+ nsresult res;
+ // Doing a special case for all items being base 10 gives ~35% speedup
+ res = (onlyBase10
+ ? ParseIPv4Number10(Substring(host, start, length - start), ipv4,
+ upperBounds[dotCount])
+ : ParseIPv4Number(Substring(host, start, length - start),
+ bases[dotCount], ipv4, upperBounds[dotCount]));
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // parse remaining parts starting from first part
+ int32_t lastUsed = -1;
+ for (int32_t i = 0; i < dotCount; i++) {
+ uint32_t number;
+ start = lastUsed + 1;
+ lastUsed = dotIndex[i];
+ res =
+ (onlyBase10 ? ParseIPv4Number10(
+ Substring(host, start, lastUsed - start), number, 255)
+ : ParseIPv4Number(Substring(host, start, lastUsed - start),
+ bases[i], number, 255));
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+ ipv4 += number << (8 * (3 - i));
+ }
+
+ // A special case for ipv4 URL like "127." should have the same result as
+ // "127".
+ if (dotCount == 1 && dotIndex[0] == length - 1) {
+ ipv4 = (ipv4 & 0xff000000) >> 24;
+ }
+
+ uint8_t ipSegments[4];
+ NetworkEndian::writeUint32(ipSegments, ipv4);
+ result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
+ ipSegments[2], ipSegments[3]);
+ return NS_OK;
+}
+
+// Return the number of "dots" in the string, or -1 if invalid. Note that the
+// number of relevant entries in the bases/starts/ends arrays is number of
+// dots + 1.
+//
+// length is assumed to be <= host.Length(); the caller is responsible for that
+//
+// Note that the value returned is guaranteed to be in [-1, 3] range.
+int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4],
+ int32_t dotIndex[3], bool& onlyBase10,
+ int32_t length, bool trailingDot) {
+ MOZ_ASSERT(length <= (int32_t)host.Length());
+ if (length <= 0) {
+ return -1;
+ }
+
+ bool lastWasNumber = false; // We count on this being false for i == 0
+ int32_t dotCount = 0;
+ onlyBase10 = true;
+
+ for (int32_t i = 0; i < length; i++) {
+ char current = host[i];
+ if (current == '.') {
+ // A dot should not follow a dot, or be first - it can follow an x though.
+ if (!(lastWasNumber ||
+ (i >= 2 && (host[i - 1] == 'X' || host[i - 1] == 'x') &&
+ host[i - 2] == '0')) ||
+ (i == (length - 1) && trailingDot)) {
+ return -1;
+ }
+
+ if (dotCount > 2) {
+ return -1;
+ }
+ lastWasNumber = false;
+ dotIndex[dotCount] = i;
+ dotCount++;
+ } else if (current == 'X' || current == 'x') {
+ if (!lastWasNumber || // An X should not follow an X or a dot or be first
+ i == (length - 1) || // No trailing Xs allowed
+ (dotCount == 0 &&
+ i != 1) || // If we had no dots, an X should be second
+ host[i - 1] != '0' || // X should always follow a 0. Guaranteed i >
+ // 0 as lastWasNumber is true
+ (dotCount > 0 &&
+ host[i - 2] != '.')) { // And that zero follows a dot if it exists
+ return -1;
+ }
+ lastWasNumber = false;
+ bases[dotCount] = 16;
+ onlyBase10 = false;
+
+ } else if (current == '0') {
+ if (i < length - 1 && // Trailing zero doesn't signal octal
+ host[i + 1] != '.' && // Lone zero is not octal
+ (i == 0 || host[i - 1] == '.')) { // Zero at start or following a dot
+ // is a candidate for octal
+ bases[dotCount] = 8; // This will turn to 16 above if X shows up
+ onlyBase10 = false;
+ }
+ lastWasNumber = true;
+
+ } else if (current >= '1' && current <= '7') {
+ lastWasNumber = true;
+
+ } else if (current >= '8' && current <= '9') {
+ if (bases[dotCount] == 8) {
+ return -1;
+ }
+ lastWasNumber = true;
+
+ } else if ((current >= 'a' && current <= 'f') ||
+ (current >= 'A' && current <= 'F')) {
+ if (bases[dotCount] != 16) {
+ return -1;
+ }
+ lastWasNumber = true;
+
+ } else {
+ return -1;
+ }
+ }
+
+ return dotCount;
+}
+
+bool ContainsOnlyAsciiDigits(const nsDependentCSubstring& input) {
+ for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
+ if (!IsAsciiDigit(*c)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring& input) {
+ for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
+ if (!IsAsciiHexDigit(*c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace mozilla::net::IPv4Parser
diff --git a/netwerk/base/IPv4Parser.h b/netwerk/base/IPv4Parser.h
new file mode 100644
index 000000000000..0325d07d2eed
--- /dev/null
+++ b/netwerk/base/IPv4Parser.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_net_IPv4Parser_h
+#define mozilla_net_IPv4Parser_h
+
+#include "nsString.h"
+
+namespace mozilla::net::IPv4Parser {
+
+bool EndsInANumber(const nsCString& input);
+nsresult NormalizeIPv4(const nsACString& host, nsCString& result);
+
+nsresult ParseIPv4Number(const nsACString& input, int32_t base,
+ uint32_t& number, uint32_t maxNumber);
+int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4],
+ int32_t dotIndex[3], bool& onlyBase10,
+ int32_t length, bool trailingDot);
+
+bool ContainsOnlyAsciiDigits(const nsDependentCSubstring& input);
+bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring& input);
+nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
+ uint32_t maxNumber);
+} // namespace mozilla::net::IPv4Parser
+
+#endif // mozilla_net_IPv4Parser_h
diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build
index dc7da96fd47d..d0fc981f7eb2 100644
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -163,6 +163,7 @@ EXPORTS.mozilla.net += [
"DashboardTypes.h",
"DefaultURI.h",
"InterceptionInfo.h",
+ "IPv4Parser.h",
"MemoryDownloader.h",
"NetworkConnectivityService.h",
"Predictor.h",
@@ -184,6 +185,7 @@ UNIFIED_SOURCES += [
"DefaultURI.cpp",
"EventTokenBucket.cpp",
"InterceptionInfo.cpp",
+ "IPv4Parser.cpp",
"LoadContextInfo.cpp",
"LoadInfo.cpp",
"MemoryDownloader.cpp",
diff --git a/netwerk/base/nsIIOService.idl b/netwerk/base/nsIIOService.idl
index 9a28aa864c40..bbf0212f048a 100644
--- a/netwerk/base/nsIIOService.idl
+++ b/netwerk/base/nsIIOService.idl
@@ -237,7 +237,10 @@ interface nsIIOService : nsISupports
boolean hostnameIsSharedIPAddress(in nsIURI aURI);
/**
- * Checks if a hostname is valid.
+ * Checks if characters not allowed in DNS are present in the hostname
+ * and if the hostname ends in a number it also checks if it's a valid
+ * IPv4 address. Any failure indicates that parsing this host will fail at a
+ * later point when using it in the URL parser.
*
* @param AUTF8String hostname is the hostname to validate
* @return true if the hostname is valid, else false
diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp
index bec236fd2a9f..87ab92645247 100644
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -67,6 +67,7 @@
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/glean/GleanMetrics.h"
#include "nsNSSComponent.h"
+#include "IPv4Parser.h"
#include "ssl.h"
#include "StaticComponents.h"
@@ -1014,8 +1015,29 @@ nsIOService::HostnameIsSharedIPAddress(nsIURI* aURI, bool* aResult) {
NS_IMETHODIMP
nsIOService::IsValidHostname(const nsACString& inHostname, bool* aResult) {
- *aResult = net_IsValidHostName(inHostname);
+ if (!net_IsValidDNSHost(inHostname)) {
+ *aResult = false;
+ return NS_OK;
+ }
+ // hostname ending with a "." delimited octet that is a number
+ // must be IPv4 or IPv6 dual address
+ nsAutoCString host(inHostname);
+ if (IPv4Parser::EndsInANumber(host)) {
+ // ipv6 dual address; for example "::1.2.3.4"
+ if (net_IsValidIPv6Addr(host)) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ nsAutoCString normalized;
+ nsresult rv = IPv4Parser::NormalizeIPv4(host, normalized);
+ if (NS_FAILED(rv)) {
+ *aResult = false;
+ return NS_OK;
+ }
+ }
+ *aResult = true;
return NS_OK;
}
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
index d4c2b253e183..80f688310190 100644
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -962,7 +962,7 @@ nsresult nsSocketTransport::ResolveHost() {
// When not resolving mHost locally, we still want to ensure that
// it only contains valid characters. See bug 304904 for details.
// Sometimes the end host is not yet known and mHost is *
- if (!net_IsValidHostName(mHost) && !mHost.EqualsLiteral("*")) {
+ if (!net_IsValidDNSHost(mHost) && !mHost.EqualsLiteral("*")) {
SOCKET_LOG((" invalid hostname %s\n", mHost.get()));
return NS_ERROR_UNKNOWN_HOST;
}
diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
index 7e868ef2919a..78a5603f6feb 100644
--- a/netwerk/base/nsStandardURL.cpp
+++ b/netwerk/base/nsStandardURL.cpp
@@ -28,10 +28,10 @@
#include "prprf.h"
#include "nsReadableUtils.h"
#include "mozilla/net/MozURL_ffi.h"
-#include "mozilla/TextUtils.h"
#include "mozilla/Utf8.h"
#include "nsIClassInfoImpl.h"
#include
+#include "IPv4Parser.h"
//
// setenv MOZ_LOG nsStandardURL:5
@@ -402,228 +402,6 @@ void nsStandardURL::InvalidateCache(bool invalidateCachedFile) {
}
}
-// Return the number of "dots" in the string, or -1 if invalid. Note that the
-// number of relevant entries in the bases/starts/ends arrays is number of
-// dots + 1.
-//
-// length is assumed to be <= host.Length(); the caller is responsible for that
-//
-// Note that the value returned is guaranteed to be in [-1, 3] range.
-inline int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4],
- int32_t dotIndex[3], bool& onlyBase10,
- int32_t length, bool trailingDot) {
- MOZ_ASSERT(length <= (int32_t)host.Length());
- if (length <= 0) {
- return -1;
- }
-
- bool lastWasNumber = false; // We count on this being false for i == 0
- int32_t dotCount = 0;
- onlyBase10 = true;
-
- for (int32_t i = 0; i < length; i++) {
- char current = host[i];
- if (current == '.') {
- // A dot should not follow a dot, or be first - it can follow an x though.
- if (!(lastWasNumber ||
- (i >= 2 && (host[i - 1] == 'X' || host[i - 1] == 'x') &&
- host[i - 2] == '0')) ||
- (i == (length - 1) && trailingDot)) {
- return -1;
- }
-
- if (dotCount > 2) {
- return -1;
- }
- lastWasNumber = false;
- dotIndex[dotCount] = i;
- dotCount++;
- } else if (current == 'X' || current == 'x') {
- if (!lastWasNumber || // An X should not follow an X or a dot or be first
- i == (length - 1) || // No trailing Xs allowed
- (dotCount == 0 &&
- i != 1) || // If we had no dots, an X should be second
- host[i - 1] != '0' || // X should always follow a 0. Guaranteed i >
- // 0 as lastWasNumber is true
- (dotCount > 0 &&
- host[i - 2] != '.')) { // And that zero follows a dot if it exists
- return -1;
- }
- lastWasNumber = false;
- bases[dotCount] = 16;
- onlyBase10 = false;
-
- } else if (current == '0') {
- if (i < length - 1 && // Trailing zero doesn't signal octal
- host[i + 1] != '.' && // Lone zero is not octal
- (i == 0 || host[i - 1] == '.')) { // Zero at start or following a dot
- // is a candidate for octal
- bases[dotCount] = 8; // This will turn to 16 above if X shows up
- onlyBase10 = false;
- }
- lastWasNumber = true;
-
- } else if (current >= '1' && current <= '7') {
- lastWasNumber = true;
-
- } else if (current >= '8' && current <= '9') {
- if (bases[dotCount] == 8) {
- return -1;
- }
- lastWasNumber = true;
-
- } else if ((current >= 'a' && current <= 'f') ||
- (current >= 'A' && current <= 'F')) {
- if (bases[dotCount] != 16) {
- return -1;
- }
- lastWasNumber = true;
-
- } else {
- return -1;
- }
- }
-
- return dotCount;
-}
-
-inline nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
- uint32_t maxNumber) {
- uint64_t value = 0;
- const char* current = input.BeginReading();
- const char* end = input.EndReading();
- for (; current < end; ++current) {
- char c = *current;
- MOZ_ASSERT(c >= '0' && c <= '9');
- value *= 10;
- value += c - '0';
- }
- if (value <= maxNumber) {
- number = value;
- return NS_OK;
- }
-
- // The error case
- number = 0;
- return NS_ERROR_FAILURE;
-}
-
-inline nsresult ParseIPv4Number(const nsACString& input, int32_t base,
- uint32_t& number, uint32_t maxNumber) {
- // Accumulate in the 64-bit value
- uint64_t value = 0;
- const char* current = input.BeginReading();
- const char* end = input.EndReading();
- switch (base) {
- case 16:
- ++current;
- [[fallthrough]];
- case 8:
- ++current;
- break;
- case 10:
- default:
- break;
- }
- for (; current < end; ++current) {
- value *= base;
- char c = *current;
- MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) ||
- (base == 8 && c >= '0' && c <= '7') ||
- (base == 16 && IsAsciiHexDigit(c)));
- if (IsAsciiDigit(c)) {
- value += c - '0';
- } else if (c >= 'a' && c <= 'f') {
- value += c - 'a' + 10;
- } else if (c >= 'A' && c <= 'F') {
- value += c - 'A' + 10;
- }
- }
-
- if (value <= maxNumber) {
- number = value;
- return NS_OK;
- }
-
- // The error case
- number = 0;
- return NS_ERROR_FAILURE;
-}
-
-// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
-/* static */
-nsresult nsStandardURL::NormalizeIPv4(const nsACString& host,
- nsCString& result) {
- int32_t bases[4] = {10, 10, 10, 10};
- bool onlyBase10 = true; // Track this as a special case
- int32_t dotIndex[3]; // The positions of the dots in the string
-
- // Use "length" rather than host.Length() after call to
- // ValidateIPv4Number because of potential trailing period.
- nsDependentCSubstring filteredHost;
- bool trailingDot = false;
- if (host.Length() > 0 && host.Last() == '.') {
- trailingDot = true;
- filteredHost.Rebind(host.BeginReading(), host.Length() - 1);
- } else {
- filteredHost.Rebind(host.BeginReading(), host.Length());
- }
-
- int32_t length = static_cast(filteredHost.Length());
- int32_t dotCount = ValidateIPv4Number(filteredHost, bases, dotIndex,
- onlyBase10, length, trailingDot);
- if (dotCount < 0 || length <= 0) {
- return NS_ERROR_FAILURE;
- }
-
- // Max values specified by the spec
- static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, 0xffffu,
- 0xffu};
- uint32_t ipv4;
- int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0);
-
- // parse the last part first
- nsresult res;
- // Doing a special case for all items being base 10 gives ~35% speedup
- res = (onlyBase10
- ? ParseIPv4Number10(Substring(host, start, length - start), ipv4,
- upperBounds[dotCount])
- : ParseIPv4Number(Substring(host, start, length - start),
- bases[dotCount], ipv4, upperBounds[dotCount]));
- if (NS_FAILED(res)) {
- return NS_ERROR_FAILURE;
- }
-
- // parse remaining parts starting from first part
- int32_t lastUsed = -1;
- for (int32_t i = 0; i < dotCount; i++) {
- uint32_t number;
- start = lastUsed + 1;
- lastUsed = dotIndex[i];
- res =
- (onlyBase10 ? ParseIPv4Number10(
- Substring(host, start, lastUsed - start), number, 255)
- : ParseIPv4Number(Substring(host, start, lastUsed - start),
- bases[i], number, 255));
- if (NS_FAILED(res)) {
- return NS_ERROR_FAILURE;
- }
- ipv4 += number << (8 * (3 - i));
- }
-
- // A special case for ipv4 URL like "127." should have the same result as
- // "127".
- if (dotCount == 1 && dotIndex[0] == length - 1) {
- ipv4 = (ipv4 & 0xff000000) >> 24;
- }
-
- uint8_t ipSegments[4];
- NetworkEndian::writeUint32(ipSegments, ipv4);
- result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
- ipSegments[2], ipSegments[3]);
- return NS_OK;
-}
-
nsIIDNService* nsStandardURL::GetIDNService() { return gIDN.get(); }
nsresult nsStandardURL::NormalizeIDN(const nsACString& aHost,
@@ -712,71 +490,6 @@ uint32_t nsStandardURL::AppendToBuf(char* buf, uint32_t i, const char* str,
return i + len;
}
-static bool ContainsOnlyAsciiDigits(const nsDependentCSubstring& input) {
- for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
- if (!IsAsciiDigit(*c)) {
- return false;
- }
- }
-
- return true;
-}
-
-static bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring& input) {
- for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
- if (!IsAsciiHexDigit(*c)) {
- return false;
- }
- }
- return true;
-}
-
-// https://url.spec.whatwg.org/#ends-in-a-number-checker
-static bool EndsInANumber(const nsCString& input) {
- // 1. Let parts be the result of strictly splitting input on U+002E (.).
- nsTArray parts;
- for (const nsDependentCSubstring& part : input.Split('.')) {
- parts.AppendElement(part);
- }
-
- if (parts.Length() == 0) {
- return false;
- }
-
- // 2.If the last item in parts is the empty string, then:
- // 1. If parts’s size is 1, then return false.
- // 2. Remove the last item from parts.
- if (parts.LastElement().IsEmpty()) {
- if (parts.Length() == 1) {
- return false;
- }
- Unused << parts.PopLastElement();
- }
-
- // 3. Let last be the last item in parts.
- const nsDependentCSubstring& last = parts.LastElement();
-
- // 4. If last is non-empty and contains only ASCII digits, then return true.
- // The erroneous input "09" will be caught by the IPv4 parser at a later
- // stage.
- if (!last.IsEmpty()) {
- if (ContainsOnlyAsciiDigits(last)) {
- return true;
- }
- }
-
- // 5. If parsing last as an IPv4 number does not return failure, then return
- // true. This is equivalent to checking that last is "0X" or "0x", followed by
- // zero or more ASCII hex digits.
- if (StringBeginsWith(last, "0x"_ns) || StringBeginsWith(last, "0X"_ns)) {
- if (ContainsOnlyAsciiHexDigits(Substring(last, 2))) {
- return true;
- }
- }
-
- return false;
-}
-
// basic algorithm:
// 1- escape url segments (for improved GetSpec efficiency)
// 2- allocate spec buffer
@@ -879,9 +592,9 @@ nsresult nsStandardURL::BuildNormalizedSpec(const char* spec,
if (NS_FAILED(rv)) {
return rv;
}
- if (EndsInANumber(encHost) && allowIp) {
+ if (IPv4Parser::EndsInANumber(encHost) && allowIp) {
nsAutoCString ipString;
- rv = NormalizeIPv4(encHost, ipString);
+ rv = IPv4Parser::NormalizeIPv4(encHost, ipString);
if (NS_FAILED(rv)) {
return rv;
}
@@ -2191,9 +1904,9 @@ nsresult nsStandardURL::SetHost(const nsACString& input) {
if (NS_FAILED(rv)) {
return rv;
}
- if (EndsInANumber(hostBuf) && allowIp) {
+ if (IPv4Parser::EndsInANumber(hostBuf) && allowIp) {
nsAutoCString ipString;
- rv = NormalizeIPv4(hostBuf, ipString);
+ rv = IPv4Parser::NormalizeIPv4(hostBuf, ipString);
if (NS_FAILED(rv)) {
return rv;
}
@@ -3980,21 +3693,3 @@ size_t nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
} // namespace net
} // namespace mozilla
-
-// For unit tests. Including nsStandardURL.h seems to cause problems
-nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result) {
- return mozilla::net::nsStandardURL::NormalizeIPv4(host, result);
-}
-
-// For unit tests. Including nsStandardURL.h seems to cause problems
-nsresult Test_ParseIPv4Number(const nsACString& input, int32_t base,
- uint32_t& number, uint32_t maxNumber) {
- return mozilla::net::ParseIPv4Number(input, base, number, maxNumber);
-}
-
-int32_t Test_ValidateIPv4Number(const nsACString& host, int32_t bases[4],
- int32_t dotIndex[3], bool& onlyBase10,
- int32_t length) {
- return mozilla::net::ValidateIPv4Number(host, bases, dotIndex, onlyBase10,
- length, false);
-}
diff --git a/netwerk/base/nsStandardURL.h b/netwerk/base/nsStandardURL.h
index 4f32fd9a7729..16adce2758b1 100644
--- a/netwerk/base/nsStandardURL.h
+++ b/netwerk/base/nsStandardURL.h
@@ -196,8 +196,6 @@ class nsStandardURL : public nsIFileURL,
};
friend class nsSegmentEncoder;
- static nsresult NormalizeIPv4(const nsACString& host, nsCString& result);
-
static nsIIDNService* GetIDNService();
protected:
diff --git a/netwerk/base/nsURLHelper.cpp b/netwerk/base/nsURLHelper.cpp
index 16bca4b92be9..2065f094ddc0 100644
--- a/netwerk/base/nsURLHelper.cpp
+++ b/netwerk/base/nsURLHelper.cpp
@@ -918,7 +918,7 @@ void net_ParseRequestContentType(const nsACString& aHeaderStr,
*aHadCharset = hadCharset;
}
-bool net_IsValidHostName(const nsACString& host) {
+bool net_IsValidDNSHost(const nsACString& host) {
// The host name is limited to 253 ascii characters.
if (host.Length() > 253) {
return false;
diff --git a/netwerk/base/nsURLHelper.h b/netwerk/base/nsURLHelper.h
index 4c6a896cb2c7..84e25f0314b8 100644
--- a/netwerk/base/nsURLHelper.h
+++ b/netwerk/base/nsURLHelper.h
@@ -219,7 +219,7 @@ inline char* net_RFindCharNotInSet(const char* str, const char* set) {
* This function returns true if the given hostname does not include any
* restricted characters. Otherwise, false is returned.
*/
-bool net_IsValidHostName(const nsACString& host);
+bool net_IsValidDNSHost(const nsACString& host);
/**
* Checks whether the IPv4 address is valid according to RFC 3986 section 3.2.2.
diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp
index d343f84a04cd..bfb58e9d6184 100644
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -524,7 +524,7 @@ nsresult nsHostResolver::ResolveHost(const nsACString& aHost,
// ensure that we are working with a valid hostname before proceeding. see
// bug 304904 for details.
- if (!net_IsValidHostName(host)) {
+ if (!net_IsValidDNSHost(host)) {
return NS_ERROR_UNKNOWN_HOST;
}
diff --git a/netwerk/protocol/http/TRRServiceChannel.cpp b/netwerk/protocol/http/TRRServiceChannel.cpp
index 3a7a6efaf30e..4bb16d4d857b 100644
--- a/netwerk/protocol/http/TRRServiceChannel.cpp
+++ b/netwerk/protocol/http/TRRServiceChannel.cpp
@@ -489,7 +489,7 @@ nsresult TRRServiceChannel::ContinueOnBeforeConnect() {
LOG(("TRRServiceChannel::ContinueOnBeforeConnect [this=%p]\n", this));
// ensure that we are using a valid hostname
- if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin()))) {
+ if (!net_IsValidDNSHost(nsDependentCString(mConnectionInfo->Origin()))) {
return NS_ERROR_UNKNOWN_HOST;
}
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
index 56046799efa2..b968ed0943eb 100644
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -883,7 +883,7 @@ nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade,
}
// ensure that we are using a valid hostname
- if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin()))) {
+ if (!net_IsValidDNSHost(nsDependentCString(mConnectionInfo->Origin()))) {
return NS_ERROR_UNKNOWN_HOST;
}
diff --git a/netwerk/test/gtest/TestStandardURL.cpp b/netwerk/test/gtest/TestStandardURL.cpp
index afefead8ef4d..d99d2257ca42 100644
--- a/netwerk/test/gtest/TestStandardURL.cpp
+++ b/netwerk/test/gtest/TestStandardURL.cpp
@@ -15,16 +15,14 @@
#include "mozilla/Base64.h"
#include "nsEscape.h"
#include "nsURLHelper.h"
+#include "IPv4Parser.h"
using namespace mozilla;
-// In nsStandardURL.cpp
-extern nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result);
-extern nsresult Test_ParseIPv4Number(const nsACString& input, int32_t base,
- uint32_t& number, uint32_t maxNumber);
-extern int32_t Test_ValidateIPv4Number(const nsACString& host, int32_t bases[4],
- int32_t dotIndex[3], bool& onlyBase10,
- int32_t length);
+nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result) {
+ return net::IPv4Parser::NormalizeIPv4(host, result);
+}
+
TEST(TestStandardURL, Simple)
{
nsCOMPtr url;
@@ -430,14 +428,14 @@ TEST(TestStandardURL, ParseIPv4Num)
int32_t dotIndex[3]; // The positions of the dots in the string
int32_t length = static_cast(host.Length());
- ASSERT_EQ(2,
- Test_ValidateIPv4Number(host, bases, dotIndex, onlyBase10, length));
+ ASSERT_EQ(2, mozilla::net::IPv4Parser::ValidateIPv4Number(
+ host, bases, dotIndex, onlyBase10, length, false));
nsCString result;
ASSERT_EQ(NS_OK, Test_NormalizeIPv4("0x.0x.0"_ns, result));
uint32_t number;
- Test_ParseIPv4Number("0x10"_ns, 16, number, 255);
+ mozilla::net::IPv4Parser::ParseIPv4Number("0x10"_ns, 16, number, 255);
ASSERT_EQ(number, (uint32_t)16);
}
diff --git a/netwerk/test/unit/test_standardurl.js b/netwerk/test/unit/test_standardurl.js
index 7dd9a6f7ff7b..4d02fb868307 100644
--- a/netwerk/test/unit/test_standardurl.js
+++ b/netwerk/test/unit/test_standardurl.js
@@ -1189,3 +1189,19 @@ add_task(async function test_bug1890346() {
let url = Services.io.newURI("file:..?/..");
equal(url.spec, "file:///?/..");
});
+
+add_task(async function test_bug1914141() {
+ equal(Services.io.isValidHostname("example.com"), true);
+ equal(Services.io.isValidHostname("example.0"), false);
+
+ equal(Services.io.isValidHostname("192.168.0.1"), true);
+ equal(Services.io.isValidHostname("192.168.0"), true);
+ equal(Services.io.isValidHostname("1.192.168.0.1"), false);
+ equal(Services.io.isValidHostname("invalid.192.168.0.1"), false);
+
+ equal(Services.io.isValidHostname("::1"), true);
+ equal(Services.io.isValidHostname("abcd::zz::00"), false);
+ equal(Services.io.isValidHostname("zzzz::1.2.3.4"), false);
+
+ equal(Services.io.isValidHostname("::1.2.3.4"), true);
+});