mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-20 23:37:33 +09:00
341 lines
12 KiB
JavaScript
341 lines
12 KiB
JavaScript
/* -*- 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/. */
|
|
|
|
/**
|
|
* Generic shared utility code for checking of IP and hostname validity.
|
|
*/
|
|
|
|
this.EXPORTED_SYMBOLS = [ "isLegalHostNameOrIP",
|
|
"isLegalHostName",
|
|
"isLegalIPv4Address",
|
|
"isLegalIPv6Address",
|
|
"isLegalIPAddress",
|
|
"isLegalLocalIPAddress",
|
|
"cleanUpHostName",
|
|
"kMinPort",
|
|
"kMaxPort" ];
|
|
|
|
var kMinPort = 1;
|
|
var kMaxPort = 65535;
|
|
|
|
/**
|
|
* Check if aHostName is an IP address or a valid hostname.
|
|
*
|
|
* @param aHostName The string to check for validity.
|
|
* @param aAllowExtendedIPFormats Allow hex/octal formats in addition to decimal.
|
|
* @return Unobscured host name if aHostName is valid.
|
|
* Returns null if it's not.
|
|
*/
|
|
function isLegalHostNameOrIP(aHostName, aAllowExtendedIPFormats)
|
|
{
|
|
/*
|
|
RFC 1123:
|
|
Whenever a user inputs the identity of an Internet host, it SHOULD
|
|
be possible to enter either (1) a host domain name or (2) an IP
|
|
address in dotted-decimal ("#.#.#.#") form. The host SHOULD check
|
|
the string syntactically for a dotted-decimal number before
|
|
looking it up in the Domain Name System.
|
|
*/
|
|
|
|
return isLegalIPAddress(aHostName, aAllowExtendedIPFormats) ||
|
|
isLegalHostName(aHostName);
|
|
}
|
|
|
|
/**
|
|
* Check if aHostName is a valid hostname.
|
|
*
|
|
* @return The host name if it is valid.
|
|
* Returns null if it's not.
|
|
*/
|
|
function isLegalHostName(aHostName)
|
|
{
|
|
/*
|
|
RFC 952:
|
|
A "name" (Net, Host, Gateway, or Domain name) is a text string up
|
|
to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
|
|
sign (-), and period (.). Note that periods are only allowed when
|
|
they serve to delimit components of "domain style names". (See
|
|
RFC-921, "Domain Name System Implementation Schedule", for
|
|
background). No blank or space characters are permitted as part of a
|
|
name. No distinction is made between upper and lower case. The first
|
|
character must be an alpha character. The last character must not be
|
|
a minus sign or period.
|
|
|
|
RFC 1123:
|
|
The syntax of a legal Internet host name was specified in RFC-952
|
|
[DNS:4]. One aspect of host name syntax is hereby changed: the
|
|
restriction on the first character is relaxed to allow either a
|
|
letter or a digit. Host software MUST support this more liberal
|
|
syntax.
|
|
|
|
Host software MUST handle host names of up to 63 characters and
|
|
SHOULD handle host names of up to 255 characters.
|
|
|
|
RFC 1034:
|
|
Relative names are either taken relative to a well known origin, or to a
|
|
list of domains used as a search list. Relative names appear mostly at
|
|
the user interface, where their interpretation varies from
|
|
implementation to implementation, and in master files, where they are
|
|
relative to a single origin domain name. The most common interpretation
|
|
uses the root "." as either the single origin or as one of the members
|
|
of the search list, so a multi-label relative name is often one where
|
|
the trailing dot has been omitted to save typing.
|
|
|
|
Since a complete domain name ends with the root label, this leads to
|
|
a printed form which ends in a dot.
|
|
*/
|
|
|
|
const hostPattern = /^(([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.?$/i;
|
|
return ((aHostName.length <= 255) && hostPattern.test(aHostName)) ? aHostName : null;
|
|
}
|
|
|
|
/**
|
|
* Check if aHostName is a valid IPv4 address.
|
|
*
|
|
* @param aHostName The string to check for validity.
|
|
* @param aAllowExtendedIPFormats If false, only IPv4 addresses in the common
|
|
decimal format (4 components, each up to 255)
|
|
* will be accepted, no hex/octal formats.
|
|
* @return Unobscured canonicalized address if aHostName is an IPv4 address.
|
|
* Returns null if it's not.
|
|
*/
|
|
function isLegalIPv4Address(aHostName, aAllowExtendedIPFormats)
|
|
{
|
|
// Scammers frequently obscure the IP address by encoding each component as
|
|
// decimal, octal, hex or in some cases a mix match of each. There can even
|
|
// be less than 4 components where the last number covers the missing components.
|
|
// See the test at mailnews/base/test/unit/test_hostnameUtils.js for possible
|
|
// combinations.
|
|
|
|
if (!aHostName)
|
|
return null;
|
|
|
|
// Break the IP address down into individual components.
|
|
let ipComponents = aHostName.split(".");
|
|
let componentCount = ipComponents.length;
|
|
if (componentCount > 4 || (componentCount < 4 && !aAllowExtendedIPFormats))
|
|
return null;
|
|
|
|
/**
|
|
* Checks validity of an IP address component.
|
|
*
|
|
* @param aValue The component string.
|
|
* @param aWidth How many components does this string cover.
|
|
* @return The value of the component in decimal if it is valid.
|
|
* Returns null if it's not.
|
|
*/
|
|
const kPowersOf256 = [ 1, 256, 65536, 16777216, 4294967296 ];
|
|
function isLegalIPv4Component(aValue, aWidth) {
|
|
let component;
|
|
// Is the component decimal?
|
|
if (/^(0|([1-9][0-9]{0,9}))$/.test(aValue)) {
|
|
component = parseInt(aValue, 10);
|
|
} else if (aAllowExtendedIPFormats) {
|
|
// Is the component octal?
|
|
if (/^(0[0-7]{1,12})$/.test(aValue))
|
|
component = parseInt(aValue, 8);
|
|
// Is the component hex?
|
|
else if (/^(0x[0-9a-f]{1,8})$/i.test(aValue))
|
|
component = parseInt(aValue, 16);
|
|
else
|
|
return null;
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
// Make sure the component in not larger than the expected maximum.
|
|
if (component >= kPowersOf256[aWidth])
|
|
return null;
|
|
|
|
return component;
|
|
}
|
|
|
|
for (let i = 0; i < componentCount; i++) {
|
|
// If we are on the last supplied component but we do not have 4,
|
|
// the last one covers the remaining ones.
|
|
let componentWidth = (i == componentCount - 1 ? 4 - i : 1);
|
|
let componentValue = isLegalIPv4Component(ipComponents[i], componentWidth);
|
|
if (componentValue == null)
|
|
return null;
|
|
|
|
// If we have a component spanning multiple ones, split it.
|
|
for (let j = 0; j < componentWidth; j++) {
|
|
ipComponents[i + j] = (componentValue >> ((componentWidth - 1 - j) * 8)) & 255;
|
|
}
|
|
}
|
|
|
|
// First component of zero is not valid.
|
|
if (ipComponents[0] == 0)
|
|
return null;
|
|
|
|
return ipComponents.join(".");
|
|
}
|
|
|
|
/**
|
|
* Check if aHostName is a valid IPv6 address.
|
|
*
|
|
* @param aHostName The string to check for validity.
|
|
* @return Unobscured canonicalized address if aHostName is an IPv6 address.
|
|
* Returns null if it's not.
|
|
*/
|
|
function isLegalIPv6Address(aHostName)
|
|
{
|
|
if (!aHostName)
|
|
return null;
|
|
|
|
// Break the IP address down into individual components.
|
|
let ipComponents = aHostName.toLowerCase().split(":");
|
|
|
|
// Make sure there are at least 3 components.
|
|
if (ipComponents.length < 3)
|
|
return null;
|
|
|
|
let ipLength = ipComponents.length - 1;
|
|
|
|
// Take care if the last part is written in decimal using dots as separators.
|
|
let lastPart = isLegalIPv4Address(ipComponents[ipLength], false);
|
|
if (lastPart)
|
|
{
|
|
let lastPartComponents = lastPart.split(".");
|
|
// Convert it into standard IPv6 components.
|
|
ipComponents[ipLength] =
|
|
((lastPartComponents[0] << 8) | lastPartComponents[1]).toString(16);
|
|
ipComponents[ipLength + 1] =
|
|
((lastPartComponents[2] << 8) | lastPartComponents[3]).toString(16);
|
|
}
|
|
|
|
// Make sure that there is only one empty component.
|
|
let emptyIndex;
|
|
for (let i = 1; i < ipComponents.length - 1; i++)
|
|
{
|
|
if (ipComponents[i] == "")
|
|
{
|
|
// If we already found an empty component return null.
|
|
if (emptyIndex)
|
|
return null;
|
|
|
|
emptyIndex = i;
|
|
}
|
|
}
|
|
|
|
// If we found an empty component, extend it.
|
|
if (emptyIndex)
|
|
{
|
|
ipComponents[emptyIndex] = 0;
|
|
|
|
// Add components so we have a total of 8.
|
|
for (let count = ipComponents.length; count < 8; count++)
|
|
ipComponents.splice(emptyIndex, 0, 0);
|
|
}
|
|
|
|
// Make sure there are 8 components.
|
|
if (ipComponents.length != 8)
|
|
return null;
|
|
|
|
// Format all components to 4 character hex value.
|
|
for (let i = 0; i < ipComponents.length; i++)
|
|
{
|
|
if (ipComponents[i] == "")
|
|
ipComponents[i] = 0;
|
|
|
|
// Make sure the component is a number and it isn't larger than 0xffff.
|
|
if (/^[0-9a-f]{1,4}$/.test(ipComponents[i])) {
|
|
ipComponents[i] = parseInt(ipComponents[i], 16);
|
|
if (isNaN(ipComponents[i]) || ipComponents[i] > 0xffff)
|
|
return null;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
|
|
// Pad the component with 0:s.
|
|
ipComponents[i] = ("0000" + ipComponents[i].toString(16)).substr(-4);
|
|
}
|
|
|
|
// TODO: support Zone indices in Link-local addresses? Currently they are rejected.
|
|
// http://en.wikipedia.org/wiki/IPv6_address#Link-local_addresses_and_zone_indices
|
|
|
|
let hostName = ipComponents.join(":");
|
|
// Treat 0000:0000:0000:0000:0000:0000:0000:0000 as an invalid IPv6 address.
|
|
return (hostName != "0000:0000:0000:0000:0000:0000:0000:0000") ?
|
|
hostName : null;
|
|
}
|
|
|
|
/**
|
|
* Check if aHostName is a valid IP address (IPv4 or IPv6).
|
|
*
|
|
* @param aHostName The string to check for validity.
|
|
* @param aAllowExtendedIPFormats Allow hex/octal formats in addition to decimal.
|
|
* @return Unobscured canonicalized IPv4 or IPv6 address if it is valid,
|
|
* otherwise null.
|
|
*/
|
|
function isLegalIPAddress(aHostName, aAllowExtendedIPFormats)
|
|
{
|
|
return isLegalIPv4Address(aHostName, aAllowExtendedIPFormats) ||
|
|
isLegalIPv6Address(aHostName);
|
|
}
|
|
|
|
/**
|
|
* Check if aIPAddress is a local or private IP address.
|
|
*
|
|
* @param aIPAddress A valid IP address literal in canonical (unobscured) form.
|
|
* @return True if it is a local/private IPv4 or IPv6 address,
|
|
* otherwise false.
|
|
*
|
|
* Note: if the passed in address is not in canonical (unobscured form),
|
|
* the result may be wrong.
|
|
*/
|
|
function isLegalLocalIPAddress(aIPAddress)
|
|
{
|
|
// IPv4 address?
|
|
let ipComponents = aIPAddress.split(".");
|
|
if (ipComponents.length == 4)
|
|
{
|
|
// Check if it's a local or private IPv4 address.
|
|
return ipComponents[0] == 10 ||
|
|
ipComponents[0] == 127 || // loopback address
|
|
(ipComponents[0] == 192 && ipComponents[1] == 168) ||
|
|
(ipComponents[0] == 169 && ipComponents[1] == 254) ||
|
|
(ipComponents[0] == 172 && ipComponents[1] >= 16 && ipComponents[1] < 32);
|
|
}
|
|
|
|
// IPv6 address?
|
|
ipComponents = aIPAddress.split(":");
|
|
if (ipComponents.length == 8)
|
|
{
|
|
// ::1/128 - localhost
|
|
if (ipComponents[0] == "0000" && ipComponents[1] == "0000" &&
|
|
ipComponents[2] == "0000" && ipComponents[3] == "0000" &&
|
|
ipComponents[4] == "0000" && ipComponents[5] == "0000" &&
|
|
ipComponents[6] == "0000" && ipComponents[7] == "0001")
|
|
return true;
|
|
|
|
// fe80::/10 - link local addresses
|
|
if (ipComponents[0] == "fe80")
|
|
return true;
|
|
|
|
// fc00::/7 - unique local addresses
|
|
if (ipComponents[0].startsWith("fc") || // usage has not been defined yet
|
|
ipComponents[0].startsWith("fd"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Clean up the hostname or IP. Usually used to sanitize a value input by the user.
|
|
* It is usually applied before we know if the hostname is even valid.
|
|
*
|
|
* @param aHostName The hostname or IP string to clean up.
|
|
*/
|
|
function cleanUpHostName(aHostName)
|
|
{
|
|
// TODO: Bug 235312: if UTF8 string was input, convert to punycode using convertUTF8toACE()
|
|
// but bug 563172 needs resolving first.
|
|
return aHostName.trim();
|
|
}
|