mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
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:
parent
7c36009cf4
commit
f2d15935d2
@ -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(
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
298
netwerk/base/IPv4Parser.cpp
Normal 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 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<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
28
netwerk/base/IPv4Parser.h
Normal 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
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 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);
|
||||
}
|
||||
|
@ -196,8 +196,6 @@ class nsStandardURL : public nsIFileURL,
|
||||
};
|
||||
friend class nsSegmentEncoder;
|
||||
|
||||
static nsresult NormalizeIPv4(const nsACString& host, nsCString& result);
|
||||
|
||||
static nsIIDNService* GetIDNService();
|
||||
|
||||
protected:
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user