Bug 1914141 - Added an ending-in-number check for non-ipv4 domains for Services.io.isValidHostname. r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D220488
This commit is contained in:
edgul 2024-09-05 21:07:34 +00:00
parent 7c36009cf4
commit f2d15935d2
18 changed files with 396 additions and 336 deletions

View File

@ -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(

View File

@ -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;
}

View File

@ -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;
}

298
netwerk/base/IPv4Parser.cpp Normal file
View File

@ -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<nsDependentCSubstring> 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 partss 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<int32_t>(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

28
netwerk/base/IPv4Parser.h Normal file
View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 <string.h>
#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<int32_t>(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<nsDependentCSubstring> 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 partss 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);
}

View File

@ -196,8 +196,6 @@ class nsStandardURL : public nsIFileURL,
};
friend class nsSegmentEncoder;
static nsresult NormalizeIPv4(const nsACString& host, nsCString& result);
static nsIIDNService* GetIDNService();
protected:

View File

@ -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;

View File

@ -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.

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<nsIURI> 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<int32_t>(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);
}

View File

@ -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);
});