1217 lines
No EOL
46 KiB
JavaScript
1217 lines
No EOL
46 KiB
JavaScript
"use strict";
|
||
/* eslint-disable prefer-destructuring */
|
||
/* eslint-disable no-param-reassign */
|
||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
}
|
||
Object.defineProperty(o, k2, desc);
|
||
}) : (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
o[k2] = m[k];
|
||
}));
|
||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
}) : function(o, v) {
|
||
o["default"] = v;
|
||
});
|
||
var __importStar = (this && this.__importStar) || function (mod) {
|
||
if (mod && mod.__esModule) return mod;
|
||
var result = {};
|
||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||
__setModuleDefault(result, mod);
|
||
return result;
|
||
};
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.Address6 = void 0;
|
||
const common = __importStar(require("./common"));
|
||
const constants4 = __importStar(require("./v4/constants"));
|
||
const constants6 = __importStar(require("./v6/constants"));
|
||
const helpers = __importStar(require("./v6/helpers"));
|
||
const ipv4_1 = require("./ipv4");
|
||
const regular_expressions_1 = require("./v6/regular-expressions");
|
||
const address_error_1 = require("./address-error");
|
||
const common_1 = require("./common");
|
||
const isCorrect6 = common.isCorrect(constants6.BITS);
|
||
function assert(condition) {
|
||
if (!condition) {
|
||
throw new Error('Assertion failed.');
|
||
}
|
||
}
|
||
function addCommas(number) {
|
||
const r = /(\d+)(\d{3})/;
|
||
while (r.test(number)) {
|
||
number = number.replace(r, '$1,$2');
|
||
}
|
||
return number;
|
||
}
|
||
function spanLeadingZeroes4(n) {
|
||
n = n.replace(/^(0{1,})([1-9]+)$/, '<span class="parse-error">$1</span>$2');
|
||
n = n.replace(/^(0{1,})(0)$/, '<span class="parse-error">$1</span>$2');
|
||
return n;
|
||
}
|
||
/*
|
||
* A helper function to compact an array
|
||
*/
|
||
function compact(address, slice) {
|
||
const s1 = [];
|
||
const s2 = [];
|
||
let i;
|
||
for (i = 0; i < address.length; i++) {
|
||
if (i < slice[0]) {
|
||
s1.push(address[i]);
|
||
}
|
||
else if (i > slice[1]) {
|
||
s2.push(address[i]);
|
||
}
|
||
}
|
||
return s1.concat(['compact']).concat(s2);
|
||
}
|
||
function paddedHex(octet) {
|
||
return parseInt(octet, 16).toString(16).padStart(4, '0');
|
||
}
|
||
function unsignByte(b) {
|
||
// eslint-disable-next-line no-bitwise
|
||
return b & 0xff;
|
||
}
|
||
/**
|
||
* Represents an IPv6 address
|
||
* @param {string} address - An IPv6 address string
|
||
* @param {number} [groups=8] - How many octets to parse
|
||
* @example
|
||
* var address = new Address6('2001::/32');
|
||
*/
|
||
class Address6 {
|
||
constructor(address, optionalGroups) {
|
||
this.addressMinusSuffix = '';
|
||
this.parsedSubnet = '';
|
||
this.subnet = '/128';
|
||
this.subnetMask = 128;
|
||
this.v4 = false;
|
||
this.zone = '';
|
||
// #region Attributes
|
||
/**
|
||
* Returns true if the given address is in the subnet of the current address
|
||
* @returns {boolean}
|
||
*/
|
||
this.isInSubnet = common.isInSubnet;
|
||
/**
|
||
* Returns true if the address is correct, false otherwise
|
||
* @returns {boolean}
|
||
*/
|
||
this.isCorrect = isCorrect6;
|
||
if (optionalGroups === undefined) {
|
||
this.groups = constants6.GROUPS;
|
||
}
|
||
else {
|
||
this.groups = optionalGroups;
|
||
}
|
||
this.address = address;
|
||
const subnet = constants6.RE_SUBNET_STRING.exec(address);
|
||
if (subnet) {
|
||
this.parsedSubnet = subnet[0].replace('/', '');
|
||
this.subnetMask = parseInt(this.parsedSubnet, 10);
|
||
this.subnet = `/${this.subnetMask}`;
|
||
if (Number.isNaN(this.subnetMask) ||
|
||
this.subnetMask < 0 ||
|
||
this.subnetMask > constants6.BITS) {
|
||
throw new address_error_1.AddressError('Invalid subnet mask.');
|
||
}
|
||
address = address.replace(constants6.RE_SUBNET_STRING, '');
|
||
}
|
||
else if (/\//.test(address)) {
|
||
throw new address_error_1.AddressError('Invalid subnet mask.');
|
||
}
|
||
const zone = constants6.RE_ZONE_STRING.exec(address);
|
||
if (zone) {
|
||
this.zone = zone[0];
|
||
address = address.replace(constants6.RE_ZONE_STRING, '');
|
||
}
|
||
this.addressMinusSuffix = address;
|
||
this.parsedAddress = this.parse(this.addressMinusSuffix);
|
||
}
|
||
/**
|
||
* Returns true if the given string is a valid IPv6 address (with optional
|
||
* CIDR subnet and zone identifier), false otherwise. Host bits in the
|
||
* subnet portion are allowed (e.g. `2001:db8::1/32` is valid); for strict
|
||
* network-address validation compare `correctForm()` to
|
||
* `startAddress().correctForm()`, or use `networkForm()`.
|
||
*/
|
||
static isValid(address) {
|
||
try {
|
||
// eslint-disable-next-line no-new
|
||
new Address6(address);
|
||
return true;
|
||
}
|
||
catch (e) {
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* Convert a BigInt to a v6 address object. The value must be in the
|
||
* range `[0, 2**128 - 1]`; otherwise `AddressError` is thrown.
|
||
* @param {bigint} bigInt - a BigInt to convert
|
||
* @returns {Address6}
|
||
* @example
|
||
* var bigInt = BigInt('1000000000000');
|
||
* var address = Address6.fromBigInt(bigInt);
|
||
* address.correctForm(); // '::e8:d4a5:1000'
|
||
*/
|
||
static fromBigInt(bigInt) {
|
||
if (bigInt < 0n || bigInt > (1n << BigInt(constants6.BITS)) - 1n) {
|
||
throw new address_error_1.AddressError('IPv6 BigInt must be in the range 0 to 2**128 - 1');
|
||
}
|
||
const hex = bigInt.toString(16).padStart(32, '0');
|
||
const groups = [];
|
||
for (let i = 0; i < constants6.GROUPS; i++) {
|
||
groups.push(hex.slice(i * 4, (i + 1) * 4));
|
||
}
|
||
return new Address6(groups.join(':'));
|
||
}
|
||
/**
|
||
* Parse a URL (with optional bracketed host and port) into an address and
|
||
* port. Returns either `{ address, port }` on success or
|
||
* `{ error, address: null, port: null }` if the URL could not be parsed.
|
||
* Ports are returned as numbers (or `null` if absent or out of range).
|
||
* @example
|
||
* var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/');
|
||
* addressAndPort.address.correctForm(); // 'ffff::'
|
||
* addressAndPort.port; // 8080
|
||
*/
|
||
static fromURL(url) {
|
||
let host;
|
||
let port = null;
|
||
let result;
|
||
// If we have brackets parse them and find a port
|
||
if (url.indexOf('[') !== -1 && url.indexOf(']:') !== -1) {
|
||
result = constants6.RE_URL_WITH_PORT.exec(url);
|
||
if (result === null) {
|
||
return {
|
||
error: 'failed to parse address with port',
|
||
address: null,
|
||
port: null,
|
||
};
|
||
}
|
||
host = result[1];
|
||
port = result[2];
|
||
// If there's a URL extract the address
|
||
}
|
||
else if (url.indexOf('/') !== -1) {
|
||
// Remove the protocol prefix
|
||
url = url.replace(/^[a-z0-9]+:\/\//, '');
|
||
// Parse the address
|
||
result = constants6.RE_URL.exec(url);
|
||
if (result === null) {
|
||
return {
|
||
error: 'failed to parse address from URL',
|
||
address: null,
|
||
port: null,
|
||
};
|
||
}
|
||
host = result[1];
|
||
// Otherwise just assign the URL to the host and let the library parse it
|
||
}
|
||
else {
|
||
host = url;
|
||
}
|
||
// If there's a port convert it to an integer
|
||
if (port) {
|
||
port = parseInt(port, 10);
|
||
// squelch out of range ports
|
||
if (port < 0 || port > 65536) {
|
||
port = null;
|
||
}
|
||
}
|
||
else {
|
||
// Standardize `undefined` to `null`
|
||
port = null;
|
||
}
|
||
return {
|
||
address: new Address6(host),
|
||
port,
|
||
};
|
||
}
|
||
/**
|
||
* Construct an `Address6` from an address and a hex subnet mask given as
|
||
* separate strings (e.g. as returned by Node's `os.networkInterfaces()`).
|
||
* Throws `AddressError` if the mask is non-contiguous (e.g.
|
||
* `ffff::ffff`).
|
||
* @example
|
||
* var address = Address6.fromAddressAndMask('fe80::1', 'ffff:ffff:ffff:ffff::');
|
||
* address.subnetMask; // 64
|
||
*/
|
||
static fromAddressAndMask(address, mask) {
|
||
const bits = common.prefixLengthFromMask(new Address6(mask).bigInt(), constants6.BITS);
|
||
return new Address6(`${address}/${bits}`);
|
||
}
|
||
/**
|
||
* Construct an `Address6` from an address and a Cisco-style wildcard mask
|
||
* given as separate strings (e.g. `::ffff:ffff:ffff:ffff` for a `/64`).
|
||
* The wildcard mask is the bitwise inverse of the subnet mask. Throws
|
||
* `AddressError` if the mask is non-contiguous.
|
||
* @example
|
||
* var address = Address6.fromAddressAndWildcardMask('fe80::1', '::ffff:ffff:ffff:ffff');
|
||
* address.subnetMask; // 64
|
||
*/
|
||
static fromAddressAndWildcardMask(address, wildcardMask) {
|
||
const wildcard = new Address6(wildcardMask).bigInt();
|
||
const allOnes = (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1);
|
||
// eslint-disable-next-line no-bitwise
|
||
const mask = wildcard ^ allOnes;
|
||
const bits = common.prefixLengthFromMask(mask, constants6.BITS);
|
||
return new Address6(`${address}/${bits}`);
|
||
}
|
||
/**
|
||
* Construct an `Address6` from a wildcard pattern with trailing `*`
|
||
* groups. The number of trailing wildcards determines the prefix
|
||
* length: each `*` represents 16 bits. `::` is expanded to zero groups
|
||
* (not wildcards) before evaluating trailing wildcards.
|
||
*
|
||
* Only trailing whole-group wildcards are supported. Partial-group
|
||
* wildcards (e.g. `2001:db8::0*`) and interior wildcards (e.g.
|
||
* `*::1`) throw `AddressError`.
|
||
* @example
|
||
* Address6.fromWildcard('2001:db8:*:*:*:*:*:*').subnet; // '/32'
|
||
* Address6.fromWildcard('2001:db8::*').subnet; // '/112'
|
||
* Address6.fromWildcard('*:*:*:*:*:*:*:*').subnet; // '/0'
|
||
*/
|
||
static fromWildcard(input) {
|
||
if (input.includes('%') || input.includes('/')) {
|
||
throw new address_error_1.AddressError('Wildcard pattern must not include a zone or CIDR suffix');
|
||
}
|
||
const halves = input.split('::');
|
||
if (halves.length > 2) {
|
||
throw new address_error_1.AddressError("Wildcard pattern cannot contain more than one '::'");
|
||
}
|
||
let groups;
|
||
if (halves.length === 2) {
|
||
const left = halves[0] === '' ? [] : halves[0].split(':');
|
||
const right = halves[1] === '' ? [] : halves[1].split(':');
|
||
const remaining = constants6.GROUPS - left.length - right.length;
|
||
if (remaining < 1) {
|
||
throw new address_error_1.AddressError("Wildcard pattern with '::' has too many groups");
|
||
}
|
||
groups = [...left, ...new Array(remaining).fill('0'), ...right];
|
||
}
|
||
else {
|
||
groups = input.split(':');
|
||
}
|
||
if (groups.length !== constants6.GROUPS) {
|
||
throw new address_error_1.AddressError('Wildcard pattern must have 8 groups');
|
||
}
|
||
let firstWildcard = -1;
|
||
for (let i = 0; i < groups.length; i++) {
|
||
if (groups[i] === '*') {
|
||
if (firstWildcard === -1) {
|
||
firstWildcard = i;
|
||
}
|
||
}
|
||
else if (firstWildcard !== -1) {
|
||
throw new address_error_1.AddressError('Wildcard `*` must only appear in trailing groups (e.g. `2001:db8:*:*:*:*:*:*`)');
|
||
}
|
||
}
|
||
const trailing = firstWildcard === -1 ? 0 : groups.length - firstWildcard;
|
||
const replaced = groups.map((g) => (g === '*' ? '0' : g));
|
||
const subnetBits = constants6.BITS - trailing * 16;
|
||
return new Address6(`${replaced.join(':')}/${subnetBits}`);
|
||
}
|
||
/**
|
||
* Create an IPv6-mapped address given an IPv4 address
|
||
* @param {string} address - An IPv4 address string
|
||
* @returns {Address6}
|
||
* @example
|
||
* var address = Address6.fromAddress4('192.168.0.1');
|
||
* address.correctForm(); // '::ffff:c0a8:1'
|
||
* address.to4in6(); // '::ffff:192.168.0.1'
|
||
*/
|
||
static fromAddress4(address) {
|
||
const address4 = new ipv4_1.Address4(address);
|
||
const mask6 = constants6.BITS - (constants4.BITS - address4.subnetMask);
|
||
return new Address6(`::ffff:${address4.correctForm()}/${mask6}`);
|
||
}
|
||
/**
|
||
* Return an address from ip6.arpa form
|
||
* @param {string} arpaFormAddress - an 'ip6.arpa' form address
|
||
* @returns {Adress6}
|
||
* @example
|
||
* var address = Address6.fromArpa(e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa.)
|
||
* address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe'
|
||
*/
|
||
static fromArpa(arpaFormAddress) {
|
||
// remove ending ".ip6.arpa." or just "."
|
||
let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, '');
|
||
const semicolonAmount = 7;
|
||
// correct ip6.arpa form with ending removed will be 63 characters
|
||
if (address.length !== 63) {
|
||
throw new address_error_1.AddressError("Invalid 'ip6.arpa' form.");
|
||
}
|
||
const parts = address.split('.').reverse();
|
||
for (let i = semicolonAmount; i > 0; i--) {
|
||
const insertIndex = i * 4;
|
||
parts.splice(insertIndex, 0, ':');
|
||
}
|
||
address = parts.join('');
|
||
return new Address6(address);
|
||
}
|
||
/**
|
||
* Return the Microsoft UNC transcription of the address
|
||
* @returns {String} the Microsoft UNC transcription of the address
|
||
*/
|
||
microsoftTranscription() {
|
||
return `${this.correctForm().replace(/:/g, '-')}.ipv6-literal.net`;
|
||
}
|
||
/**
|
||
* Return the first n bits of the address, defaulting to the subnet mask
|
||
* @param {number} [mask=subnet] - the number of bits to mask
|
||
* @returns {String} the first n bits of the address as a string
|
||
*/
|
||
mask(mask = this.subnetMask) {
|
||
return this.getBitsBase2(0, mask);
|
||
}
|
||
/**
|
||
* Return the number of possible subnets of a given size in the address
|
||
* @param {number} [subnetSize=128] - the subnet size
|
||
* @returns {String}
|
||
*/
|
||
// TODO: probably useful to have a numeric version of this too
|
||
possibleSubnets(subnetSize = 128) {
|
||
const availableBits = constants6.BITS - this.subnetMask;
|
||
const subnetBits = Math.abs(subnetSize - constants6.BITS);
|
||
const subnetPowers = availableBits - subnetBits;
|
||
if (subnetPowers < 0) {
|
||
return '0';
|
||
}
|
||
return addCommas((BigInt('2') ** BigInt(subnetPowers)).toString(10));
|
||
}
|
||
/**
|
||
* Helper function getting start address.
|
||
* @returns {bigint}
|
||
*/
|
||
_startAddress() {
|
||
return BigInt(`0b${this.mask() + '0'.repeat(constants6.BITS - this.subnetMask)}`);
|
||
}
|
||
/**
|
||
* The first address in the range given by this address' subnet
|
||
* Often referred to as the Network Address.
|
||
* @returns {Address6}
|
||
*/
|
||
startAddress() {
|
||
return Address6.fromBigInt(this._startAddress());
|
||
}
|
||
/**
|
||
* The first host address in the range given by this address's subnet ie
|
||
* the first address after the Network Address
|
||
* @returns {Address6}
|
||
*/
|
||
startAddressExclusive() {
|
||
const adjust = BigInt('1');
|
||
return Address6.fromBigInt(this._startAddress() + adjust);
|
||
}
|
||
/**
|
||
* Helper function getting end address.
|
||
* @returns {bigint}
|
||
*/
|
||
_endAddress() {
|
||
return BigInt(`0b${this.mask() + '1'.repeat(constants6.BITS - this.subnetMask)}`);
|
||
}
|
||
/**
|
||
* The last address in the range given by this address' subnet
|
||
* Often referred to as the Broadcast
|
||
* @returns {Address6}
|
||
*/
|
||
endAddress() {
|
||
return Address6.fromBigInt(this._endAddress());
|
||
}
|
||
/**
|
||
* The last host address in the range given by this address's subnet ie
|
||
* the last address prior to the Broadcast Address
|
||
* @returns {Address6}
|
||
*/
|
||
endAddressExclusive() {
|
||
const adjust = BigInt('1');
|
||
return Address6.fromBigInt(this._endAddress() - adjust);
|
||
}
|
||
/**
|
||
* The hex form of the subnet mask, e.g. `ffff:ffff:ffff:ffff::` for a
|
||
* `/64`. Returns an `Address6`; call `.correctForm()` for the string.
|
||
* @returns {Address6}
|
||
*/
|
||
subnetMaskAddress() {
|
||
return Address6.fromBigInt(BigInt(`0b${'1'.repeat(this.subnetMask)}${'0'.repeat(constants6.BITS - this.subnetMask)}`));
|
||
}
|
||
/**
|
||
* The Cisco-style wildcard mask, e.g. `::ffff:ffff:ffff:ffff` for a
|
||
* `/64`. This is the bitwise inverse of `subnetMaskAddress()`. Returns
|
||
* an `Address6`; call `.correctForm()` for the string.
|
||
* @returns {Address6}
|
||
*/
|
||
wildcardMask() {
|
||
return Address6.fromBigInt(BigInt(`0b${'0'.repeat(this.subnetMask)}${'1'.repeat(constants6.BITS - this.subnetMask)}`));
|
||
}
|
||
/**
|
||
* The network address in CIDR string form, e.g. `2001:db8::/32` for
|
||
* `2001:db8::1/32`. For an address with no explicit subnet the prefix
|
||
* is `/128`, e.g. `networkForm()` on `2001:db8::1` returns
|
||
* `2001:db8::1/128`.
|
||
* @returns {string}
|
||
*/
|
||
networkForm() {
|
||
return `${this.startAddress().correctForm()}/${this.subnetMask}`;
|
||
}
|
||
/**
|
||
* Return the scope of the address. The 4-bit scope field
|
||
* ([RFC 4291 §2.7](https://datatracker.ietf.org/doc/html/rfc4291#section-2.7))
|
||
* is only defined for multicast addresses; for unicast addresses the scope
|
||
* is derived from the address type per
|
||
* [RFC 4007 §6](https://datatracker.ietf.org/doc/html/rfc4007#section-6).
|
||
* @returns {String}
|
||
*/
|
||
getScope() {
|
||
const type = this.getType();
|
||
if (type === 'Multicast' || type.startsWith('Multicast ')) {
|
||
const scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
|
||
return scope || 'Unknown';
|
||
}
|
||
// RFC 4291 §2.5.3: the loopback address is treated as having Link-Local
|
||
// scope. (Multicast scope 1, "Interface-Local", is a different concept
|
||
// used only for loopback transmission of multicast.)
|
||
if (type === 'Link-local unicast' || type === 'Loopback') {
|
||
return 'Link local';
|
||
}
|
||
// RFC 4007 §6: the unspecified address has no scope.
|
||
if (type === 'Unspecified') {
|
||
return 'Unknown';
|
||
}
|
||
return 'Global';
|
||
}
|
||
/**
|
||
* Return the type of the address
|
||
* @returns {String}
|
||
*/
|
||
getType() {
|
||
for (let i = 0; i < TYPE_SUBNETS.length; i++) {
|
||
const entry = TYPE_SUBNETS[i];
|
||
if (this.isInSubnet(entry[0])) {
|
||
return entry[1];
|
||
}
|
||
}
|
||
return 'Global unicast';
|
||
}
|
||
/**
|
||
* Return the bits in the given range as a BigInt
|
||
* @returns {bigint}
|
||
*/
|
||
getBits(start, end) {
|
||
return BigInt(`0b${this.getBitsBase2(start, end)}`);
|
||
}
|
||
/**
|
||
* Return the bits in the given range as a base-2 string
|
||
* @returns {String}
|
||
*/
|
||
getBitsBase2(start, end) {
|
||
return this.binaryZeroPad().slice(start, end);
|
||
}
|
||
/**
|
||
* Return the bits in the given range as a base-16 string
|
||
* @returns {String}
|
||
*/
|
||
getBitsBase16(start, end) {
|
||
const length = end - start;
|
||
if (length % 4 !== 0) {
|
||
throw new Error('Length of bits to retrieve must be divisible by four');
|
||
}
|
||
return this.getBits(start, end)
|
||
.toString(16)
|
||
.padStart(length / 4, '0');
|
||
}
|
||
/**
|
||
* Return the bits that are set past the subnet mask length
|
||
* @returns {String}
|
||
*/
|
||
getBitsPastSubnet() {
|
||
return this.getBitsBase2(this.subnetMask, constants6.BITS);
|
||
}
|
||
/**
|
||
* Return the reversed ip6.arpa form of the address
|
||
* @param {Object} options
|
||
* @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix
|
||
* @returns {String}
|
||
*/
|
||
reverseForm(options) {
|
||
if (!options) {
|
||
options = {};
|
||
}
|
||
const characters = Math.floor(this.subnetMask / 4);
|
||
const reversed = this.canonicalForm()
|
||
.replace(/:/g, '')
|
||
.split('')
|
||
.slice(0, characters)
|
||
.reverse()
|
||
.join('.');
|
||
if (characters > 0) {
|
||
if (options.omitSuffix) {
|
||
return reversed;
|
||
}
|
||
return `${reversed}.ip6.arpa.`;
|
||
}
|
||
if (options.omitSuffix) {
|
||
return '';
|
||
}
|
||
return 'ip6.arpa.';
|
||
}
|
||
/**
|
||
* Returns the address in correct form, per
|
||
* [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952): leading zeros
|
||
* stripped, the longest run of zero groups collapsed to `::`, and hex digits
|
||
* lowercased (e.g. `2001:db8::1`). This is the recommended form for display.
|
||
*/
|
||
correctForm() {
|
||
let i;
|
||
let groups = [];
|
||
let zeroCounter = 0;
|
||
const zeroes = [];
|
||
for (i = 0; i < this.parsedAddress.length; i++) {
|
||
const value = parseInt(this.parsedAddress[i], 16);
|
||
if (value === 0) {
|
||
zeroCounter++;
|
||
}
|
||
if (value !== 0 && zeroCounter > 0) {
|
||
if (zeroCounter > 1) {
|
||
zeroes.push([i - zeroCounter, i - 1]);
|
||
}
|
||
zeroCounter = 0;
|
||
}
|
||
}
|
||
// Do we end with a string of zeroes?
|
||
if (zeroCounter > 1) {
|
||
zeroes.push([this.parsedAddress.length - zeroCounter, this.parsedAddress.length - 1]);
|
||
}
|
||
const zeroLengths = zeroes.map((n) => n[1] - n[0] + 1);
|
||
if (zeroes.length > 0) {
|
||
const index = zeroLengths.indexOf(Math.max(...zeroLengths));
|
||
groups = compact(this.parsedAddress, zeroes[index]);
|
||
}
|
||
else {
|
||
groups = this.parsedAddress;
|
||
}
|
||
for (i = 0; i < groups.length; i++) {
|
||
if (groups[i] !== 'compact') {
|
||
groups[i] = parseInt(groups[i], 16).toString(16);
|
||
}
|
||
}
|
||
let correct = groups.join(':');
|
||
correct = correct.replace(/^compact$/, '::');
|
||
correct = correct.replace(/(^compact)|(compact$)/, ':');
|
||
correct = correct.replace(/compact/, '');
|
||
return correct;
|
||
}
|
||
/**
|
||
* Return a zero-padded base-2 string representation of the address
|
||
* @returns {String}
|
||
* @example
|
||
* var address = new Address6('2001:4860:4001:803::1011');
|
||
* address.binaryZeroPad();
|
||
* // '0010000000000001010010000110000001000000000000010000100000000011
|
||
* // 0000000000000000000000000000000000000000000000000001000000010001'
|
||
*/
|
||
binaryZeroPad() {
|
||
if (this._binaryZeroPad === undefined) {
|
||
this._binaryZeroPad = this.bigInt().toString(2).padStart(constants6.BITS, '0');
|
||
}
|
||
return this._binaryZeroPad;
|
||
}
|
||
/**
|
||
* Parses a v4-in-v6 string (e.g. `::ffff:192.168.0.1`) by extracting the
|
||
* trailing IPv4 address into `this.address4` / `this.parsedAddress4` and
|
||
* returning the address with the v4 portion converted to two v6 groups.
|
||
* Used internally by `parse()`.
|
||
*/
|
||
// TODO: Improve the semantics of this helper function
|
||
parse4in6(address) {
|
||
if (address.indexOf('.') === -1) {
|
||
return address;
|
||
}
|
||
const groups = address.split(':');
|
||
const lastGroup = groups.slice(-1)[0];
|
||
const address4 = lastGroup.match(constants4.RE_ADDRESS);
|
||
if (address4) {
|
||
this.parsedAddress4 = address4[0];
|
||
this.address4 = new ipv4_1.Address4(this.parsedAddress4);
|
||
for (let i = 0; i < this.address4.groups; i++) {
|
||
if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) {
|
||
// The prefix groups haven't been through the bad-character check
|
||
// yet, so escape them before including in the error HTML.
|
||
const highlighted = this.address4.parsedAddress.map(spanLeadingZeroes4).join('.');
|
||
const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':');
|
||
const separator = groups.length > 1 ? ':' : '';
|
||
throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`);
|
||
}
|
||
}
|
||
this.v4 = true;
|
||
groups[groups.length - 1] = this.address4.toGroup6();
|
||
address = groups.join(':');
|
||
}
|
||
return address;
|
||
}
|
||
/**
|
||
* Parses an IPv6 address string into its 8 hexadecimal groups (expanding
|
||
* any `::` elision and any trailing v4-in-v6 portion) and stores the result
|
||
* on `this.parsedAddress`. Called automatically by the constructor; you
|
||
* typically don't need to call it directly. Throws `AddressError` if the
|
||
* input is malformed.
|
||
*/
|
||
// TODO: Make private?
|
||
parse(address) {
|
||
address = this.parse4in6(address);
|
||
const badCharacters = address.match(constants6.RE_BAD_CHARACTERS);
|
||
if (badCharacters) {
|
||
throw new address_error_1.AddressError(`Bad character${badCharacters.length > 1 ? 's' : ''} detected in address: ${badCharacters.join('')}`, address.replace(constants6.RE_BAD_CHARACTERS, '<span class="parse-error">$1</span>'));
|
||
}
|
||
const badAddress = address.match(constants6.RE_BAD_ADDRESS);
|
||
if (badAddress) {
|
||
throw new address_error_1.AddressError(`Address failed regex: ${badAddress.join('')}`, address.replace(constants6.RE_BAD_ADDRESS, '<span class="parse-error">$1</span>'));
|
||
}
|
||
let groups = [];
|
||
const halves = address.split('::');
|
||
if (halves.length === 2) {
|
||
let first = halves[0].split(':');
|
||
let last = halves[1].split(':');
|
||
if (first.length === 1 && first[0] === '') {
|
||
first = [];
|
||
}
|
||
if (last.length === 1 && last[0] === '') {
|
||
last = [];
|
||
}
|
||
const remaining = this.groups - (first.length + last.length);
|
||
if (!remaining) {
|
||
throw new address_error_1.AddressError('Error parsing groups');
|
||
}
|
||
this.elidedGroups = remaining;
|
||
this.elisionBegin = first.length;
|
||
this.elisionEnd = first.length + this.elidedGroups;
|
||
groups = groups.concat(first);
|
||
for (let i = 0; i < remaining; i++) {
|
||
groups.push('0');
|
||
}
|
||
groups = groups.concat(last);
|
||
}
|
||
else if (halves.length === 1) {
|
||
groups = address.split(':');
|
||
this.elidedGroups = 0;
|
||
}
|
||
else {
|
||
throw new address_error_1.AddressError('Too many :: groups found');
|
||
}
|
||
groups = groups.map((group) => parseInt(group, 16).toString(16));
|
||
if (groups.length !== this.groups) {
|
||
throw new address_error_1.AddressError('Incorrect number of groups found');
|
||
}
|
||
return groups;
|
||
}
|
||
/**
|
||
* Returns the canonical (fully expanded) form of the address: all 8 groups,
|
||
* each padded to 4 hex digits, with no `::` collapsing
|
||
* (e.g. `2001:0db8:0000:0000:0000:0000:0000:0001`). Useful for sorting and
|
||
* byte-exact comparison.
|
||
*/
|
||
canonicalForm() {
|
||
return this.parsedAddress.map(paddedHex).join(':');
|
||
}
|
||
/**
|
||
* Return the decimal form of the address
|
||
* @returns {String}
|
||
*/
|
||
decimal() {
|
||
return this.parsedAddress.map((n) => parseInt(n, 16).toString(10).padStart(5, '0')).join(':');
|
||
}
|
||
/**
|
||
* Return the address as a BigInt
|
||
* @returns {bigint}
|
||
*/
|
||
bigInt() {
|
||
return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`);
|
||
}
|
||
/**
|
||
* Return the last two groups of this address as an IPv4 address string
|
||
* @returns {Address4}
|
||
* @example
|
||
* var address = new Address6('2001:4860:4001::1825:bf11');
|
||
* address.to4().correctForm(); // '24.37.191.17'
|
||
*/
|
||
to4() {
|
||
const binary = this.binaryZeroPad().split('');
|
||
return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16).padStart(8, '0'));
|
||
}
|
||
/**
|
||
* Return the v4-in-v6 form of the address
|
||
* @returns {String}
|
||
*/
|
||
to4in6() {
|
||
const address4 = this.to4();
|
||
const address6 = new Address6(this.parsedAddress.slice(0, 6).join(':'), 6);
|
||
const correct = address6.correctForm();
|
||
let infix = '';
|
||
if (!/:$/.test(correct)) {
|
||
infix = ':';
|
||
}
|
||
return correct + infix + address4.address;
|
||
}
|
||
/**
|
||
* Decodes the Teredo tunneling fields embedded in this address. Returns the
|
||
* Teredo prefix, server IPv4, client IPv4, raw flag bits, cone-NAT flag,
|
||
* UDP port, and Microsoft-format flag breakdown (reserved, universal/local,
|
||
* group/individual, nonce). Only meaningful for addresses in `2001::/32`.
|
||
*/
|
||
inspectTeredo() {
|
||
/*
|
||
- Bits 0 to 31 are set to the Teredo prefix (normally 2001:0000::/32).
|
||
- Bits 32 to 63 embed the primary IPv4 address of the Teredo server that
|
||
is used.
|
||
- Bits 64 to 79 can be used to define some flags. Currently only the
|
||
higher order bit is used; it is set to 1 if the Teredo client is
|
||
located behind a cone NAT, 0 otherwise. For Microsoft's Windows Vista
|
||
and Windows Server 2008 implementations, more bits are used. In those
|
||
implementations, the format for these 16 bits is "CRAAAAUG AAAAAAAA",
|
||
where "C" remains the "Cone" flag. The "R" bit is reserved for future
|
||
use. The "U" bit is for the Universal/Local flag (set to 0). The "G" bit
|
||
is Individual/Group flag (set to 0). The A bits are set to a 12-bit
|
||
randomly generated number chosen by the Teredo client to introduce
|
||
additional protection for the Teredo node against IPv6-based scanning
|
||
attacks.
|
||
- Bits 80 to 95 contains the obfuscated UDP port number. This is the
|
||
port number that is mapped by the NAT to the Teredo client with all
|
||
bits inverted.
|
||
- Bits 96 to 127 contains the obfuscated IPv4 address. This is the
|
||
public IPv4 address of the NAT with all bits inverted.
|
||
*/
|
||
const prefix = this.getBitsBase16(0, 32);
|
||
const bitsForUdpPort = this.getBits(80, 96);
|
||
// eslint-disable-next-line no-bitwise
|
||
const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString();
|
||
const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64));
|
||
const bitsForClient4 = this.getBits(96, 128);
|
||
// eslint-disable-next-line no-bitwise
|
||
const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16).padStart(8, '0'));
|
||
const flagsBase2 = this.getBitsBase2(64, 80);
|
||
const coneNat = (0, common_1.testBit)(flagsBase2, 15);
|
||
const reserved = (0, common_1.testBit)(flagsBase2, 14);
|
||
const groupIndividual = (0, common_1.testBit)(flagsBase2, 8);
|
||
const universalLocal = (0, common_1.testBit)(flagsBase2, 9);
|
||
const nonce = BigInt(`0b${flagsBase2.slice(2, 6) + flagsBase2.slice(8, 16)}`).toString(10);
|
||
return {
|
||
prefix: `${prefix.slice(0, 4)}:${prefix.slice(4, 8)}`,
|
||
server4: server4.address,
|
||
client4: client4.address,
|
||
flags: flagsBase2,
|
||
coneNat,
|
||
microsoft: {
|
||
reserved,
|
||
universalLocal,
|
||
groupIndividual,
|
||
nonce,
|
||
},
|
||
udpPort,
|
||
};
|
||
}
|
||
/**
|
||
* Decodes the 6to4 tunneling fields embedded in this address. Returns the
|
||
* 6to4 prefix and the embedded IPv4 gateway address. Only meaningful for
|
||
* addresses in `2002::/16`.
|
||
*/
|
||
inspect6to4() {
|
||
/*
|
||
- Bits 0 to 15 are set to the 6to4 prefix (2002::/16).
|
||
- Bits 16 to 48 embed the IPv4 address of the 6to4 gateway that is used.
|
||
*/
|
||
const prefix = this.getBitsBase16(0, 16);
|
||
const gateway = ipv4_1.Address4.fromHex(this.getBitsBase16(16, 48));
|
||
return {
|
||
prefix: prefix.slice(0, 4),
|
||
gateway: gateway.address,
|
||
};
|
||
}
|
||
/**
|
||
* Return a v6 6to4 address from a v6 v4inv6 address
|
||
* @returns {Address6}
|
||
*/
|
||
to6to4() {
|
||
if (!this.is4()) {
|
||
return null;
|
||
}
|
||
const addr6to4 = [
|
||
'2002',
|
||
this.getBitsBase16(96, 112),
|
||
this.getBitsBase16(112, 128),
|
||
'',
|
||
'/16',
|
||
].join(':');
|
||
return new Address6(addr6to4);
|
||
}
|
||
/**
|
||
* Embed an IPv4 address into a NAT64 IPv6 address using the encoding
|
||
* defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
|
||
* The default prefix is the well-known prefix `64:ff9b::/96`. The prefix
|
||
* length must be one of 32, 40, 48, 56, 64, or 96; for prefixes shorter
|
||
* than /64 the IPv4 octets are split around the reserved bits 64–71.
|
||
* @example
|
||
* Address6.fromAddress4Nat64('192.0.2.33').correctForm(); // '64:ff9b::c000:221'
|
||
* Address6.fromAddress4Nat64('192.0.2.33', '2001:db8::/32').correctForm(); // '2001:db8:c000:221::'
|
||
*/
|
||
static fromAddress4Nat64(address, prefix = '64:ff9b::/96') {
|
||
const v4 = new ipv4_1.Address4(address);
|
||
const prefix6 = new Address6(prefix);
|
||
const pl = prefix6.subnetMask;
|
||
if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) {
|
||
throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96');
|
||
}
|
||
const prefixBits = prefix6.binaryZeroPad();
|
||
const v4Bits = v4.binaryZeroPad();
|
||
let bits;
|
||
if (pl === 96) {
|
||
bits = prefixBits.slice(0, 96) + v4Bits;
|
||
}
|
||
else {
|
||
const beforeU = 64 - pl;
|
||
bits =
|
||
prefixBits.slice(0, pl) +
|
||
v4Bits.slice(0, beforeU) +
|
||
'00000000' +
|
||
v4Bits.slice(beforeU) +
|
||
'0'.repeat(128 - 72 - (32 - beforeU));
|
||
}
|
||
const hex = BigInt(`0b${bits}`).toString(16).padStart(32, '0');
|
||
const groups = [];
|
||
for (let i = 0; i < 8; i++) {
|
||
groups.push(hex.slice(i * 4, (i + 1) * 4));
|
||
}
|
||
return new Address6(groups.join(':'));
|
||
}
|
||
/**
|
||
* Extract the embedded IPv4 address from a NAT64 IPv6 address using the
|
||
* encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
|
||
* The default prefix is the well-known prefix `64:ff9b::/96`. Returns
|
||
* `null` if this address is not contained within the given prefix.
|
||
* @example
|
||
* new Address6('64:ff9b::c000:221').toAddress4Nat64()!.correctForm(); // '192.0.2.33'
|
||
*/
|
||
toAddress4Nat64(prefix = '64:ff9b::/96') {
|
||
const prefix6 = new Address6(prefix);
|
||
const pl = prefix6.subnetMask;
|
||
if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) {
|
||
throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96');
|
||
}
|
||
if (!this.isInSubnet(prefix6)) {
|
||
return null;
|
||
}
|
||
const bits = this.binaryZeroPad();
|
||
let v4Bits;
|
||
if (pl === 96) {
|
||
v4Bits = bits.slice(96, 128);
|
||
}
|
||
else {
|
||
const beforeU = 64 - pl;
|
||
v4Bits = bits.slice(pl, pl + beforeU) + bits.slice(72, 72 + (32 - beforeU));
|
||
}
|
||
const octets = [];
|
||
for (let i = 0; i < 4; i++) {
|
||
octets.push(parseInt(v4Bits.slice(i * 8, (i + 1) * 8), 2).toString());
|
||
}
|
||
return new ipv4_1.Address4(octets.join('.'));
|
||
}
|
||
/**
|
||
* Return a byte array.
|
||
*
|
||
* To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toByteArray())`.
|
||
* @returns {Array}
|
||
*/
|
||
toByteArray() {
|
||
const valueWithoutPadding = this.bigInt().toString(16);
|
||
const leadingPad = '0'.repeat(valueWithoutPadding.length % 2);
|
||
const value = `${leadingPad}${valueWithoutPadding}`;
|
||
const bytes = [];
|
||
for (let i = 0, length = value.length; i < length; i += 2) {
|
||
bytes.push(parseInt(value.substring(i, i + 2), 16));
|
||
}
|
||
return bytes;
|
||
}
|
||
/**
|
||
* Return an unsigned byte array.
|
||
*
|
||
* To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toUnsignedByteArray())`.
|
||
* @returns {Array}
|
||
*/
|
||
toUnsignedByteArray() {
|
||
return this.toByteArray().map(unsignByte);
|
||
}
|
||
/**
|
||
* Convert a byte array to an Address6 object.
|
||
*
|
||
* To convert from a Node.js `Buffer`, spread it: `Address6.fromByteArray([...buf])`.
|
||
* @returns {Address6}
|
||
*/
|
||
static fromByteArray(bytes) {
|
||
return this.fromUnsignedByteArray(bytes.map(unsignByte));
|
||
}
|
||
/**
|
||
* Convert an unsigned byte array to an Address6 object.
|
||
*
|
||
* To convert from a Node.js `Buffer`, spread it: `Address6.fromUnsignedByteArray([...buf])`.
|
||
* @returns {Address6}
|
||
*/
|
||
static fromUnsignedByteArray(bytes) {
|
||
const BYTE_MAX = BigInt('256');
|
||
let result = BigInt('0');
|
||
let multiplier = BigInt('1');
|
||
for (let i = bytes.length - 1; i >= 0; i--) {
|
||
result += multiplier * BigInt(bytes[i].toString(10));
|
||
multiplier *= BYTE_MAX;
|
||
}
|
||
return Address6.fromBigInt(result);
|
||
}
|
||
/**
|
||
* Returns true if the address is in the canonical form, false otherwise
|
||
* @returns {boolean}
|
||
*/
|
||
isCanonical() {
|
||
return this.addressMinusSuffix === this.canonicalForm();
|
||
}
|
||
/**
|
||
* Returns true if the address is a link local address, false otherwise
|
||
* @returns {boolean}
|
||
*/
|
||
isLinkLocal() {
|
||
// Zeroes are required, i.e. we can't check isInSubnet with 'fe80::/10'
|
||
if (this.getBitsBase2(0, 64) ===
|
||
'1111111010000000000000000000000000000000000000000000000000000000') {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* Returns true if the address is a multicast address, false otherwise
|
||
* @returns {boolean}
|
||
*/
|
||
isMulticast() {
|
||
const type = this.getType();
|
||
return type === 'Multicast' || type.startsWith('Multicast ');
|
||
}
|
||
/**
|
||
* Returns true if the address was written in v4-in-v6 dotted-quad notation
|
||
* (e.g. `::ffff:127.0.0.1`), false otherwise. This is a notation-level flag
|
||
* and does not reflect whether the address bits lie in the IPv4-mapped
|
||
* (`::ffff:0:0/96`) subnet — for that, see {@link isMapped4}.
|
||
* @returns {boolean}
|
||
*/
|
||
is4() {
|
||
return this.v4;
|
||
}
|
||
/**
|
||
* Returns true if the address is an IPv4-mapped IPv6 address in
|
||
* `::ffff:0:0/96` ([RFC 4291 §2.5.5.2](https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2)),
|
||
* false otherwise. Unlike {@link is4}, this checks the underlying address
|
||
* bits rather than the textual notation, so `::ffff:127.0.0.1` and
|
||
* `::ffff:7f00:1` both return true.
|
||
* @returns {boolean}
|
||
*/
|
||
isMapped4() {
|
||
return this.isInSubnet(IPV4_MAPPED_SUBNET);
|
||
}
|
||
/**
|
||
* Returns true if the address is a Teredo address, false otherwise
|
||
* @returns {boolean}
|
||
*/
|
||
isTeredo() {
|
||
return this.isInSubnet(TEREDO_SUBNET);
|
||
}
|
||
/**
|
||
* Returns true if the address is a 6to4 address, false otherwise
|
||
* @returns {boolean}
|
||
*/
|
||
is6to4() {
|
||
return this.isInSubnet(SIX_TO_FOUR_SUBNET);
|
||
}
|
||
/**
|
||
* Returns true if the address is a loopback address, false otherwise
|
||
* @returns {boolean}
|
||
*/
|
||
isLoopback() {
|
||
return this.getType() === 'Loopback';
|
||
}
|
||
/**
|
||
* Returns true if the address is a Unique Local Address in `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)). ULAs are the IPv6 equivalent of IPv4 [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private addresses.
|
||
* @returns {boolean}
|
||
*/
|
||
isULA() {
|
||
return this.isInSubnet(ULA_SUBNET);
|
||
}
|
||
/**
|
||
* Returns true if the address is the unspecified address `::`.
|
||
* @returns {boolean}
|
||
*/
|
||
isUnspecified() {
|
||
return this.getType() === 'Unspecified';
|
||
}
|
||
/**
|
||
* Returns true if the address is in the documentation prefix `2001:db8::/32` ([RFC 3849](https://datatracker.ietf.org/doc/html/rfc3849)).
|
||
* @returns {boolean}
|
||
*/
|
||
isDocumentation() {
|
||
return this.isInSubnet(DOCUMENTATION_SUBNET);
|
||
}
|
||
// #endregion
|
||
// #region HTML
|
||
/**
|
||
* Returns the address as an HTTP URL with the host bracketed, e.g.
|
||
* `http://[2001:db8::1]/`. If `optionalPort` is provided it is appended,
|
||
* e.g. `http://[2001:db8::1]:8080/`.
|
||
*/
|
||
href(optionalPort) {
|
||
if (optionalPort === undefined) {
|
||
optionalPort = '';
|
||
}
|
||
else {
|
||
optionalPort = `:${optionalPort}`;
|
||
}
|
||
return `http://[${this.correctForm()}]${optionalPort}/`;
|
||
}
|
||
/**
|
||
* Returns an HTML `<a>` element whose `href` encodes the address in a URL
|
||
* hash fragment (default prefix `/#address=`). Useful for linking between
|
||
* pages of an address-inspector UI.
|
||
* @param options.className - CSS class for the rendered `<a>` element
|
||
* @param options.prefix - hash prefix prepended to the address (default `/#address=`)
|
||
* @param options.v4 - when true, render the address in v4-in-v6 form
|
||
*/
|
||
link(options) {
|
||
if (!options) {
|
||
options = {};
|
||
}
|
||
if (options.className === undefined) {
|
||
options.className = '';
|
||
}
|
||
if (options.prefix === undefined) {
|
||
options.prefix = '/#address=';
|
||
}
|
||
if (options.v4 === undefined) {
|
||
options.v4 = false;
|
||
}
|
||
let formFunction = this.correctForm;
|
||
if (options.v4) {
|
||
formFunction = this.to4in6;
|
||
}
|
||
const form = formFunction.call(this);
|
||
const safeHref = helpers.escapeHtml(`${options.prefix}${form}`);
|
||
const safeForm = helpers.escapeHtml(form);
|
||
if (options.className) {
|
||
const safeClass = helpers.escapeHtml(options.className);
|
||
return `<a href="${safeHref}" class="${safeClass}">${safeForm}</a>`;
|
||
}
|
||
return `<a href="${safeHref}">${safeForm}</a>`;
|
||
}
|
||
/**
|
||
* Groups an address
|
||
* @returns {String}
|
||
*/
|
||
group() {
|
||
if (this.elidedGroups === 0) {
|
||
// The simple case
|
||
return helpers.simpleGroup(this.addressMinusSuffix).join(':');
|
||
}
|
||
assert(typeof this.elidedGroups === 'number');
|
||
assert(typeof this.elisionBegin === 'number');
|
||
// The elided case
|
||
const output = [];
|
||
const [left, right] = this.addressMinusSuffix.split('::');
|
||
if (left.length) {
|
||
output.push(...helpers.simpleGroup(left));
|
||
}
|
||
else {
|
||
output.push('');
|
||
}
|
||
const classes = ['hover-group'];
|
||
for (let i = this.elisionBegin; i < this.elisionBegin + this.elidedGroups; i++) {
|
||
classes.push(`group-${i}`);
|
||
}
|
||
output.push(`<span class="${classes.join(' ')}"></span>`);
|
||
if (right.length) {
|
||
output.push(...helpers.simpleGroup(right, this.elisionEnd));
|
||
}
|
||
else {
|
||
output.push('');
|
||
}
|
||
if (this.is4()) {
|
||
assert(this.address4 instanceof ipv4_1.Address4);
|
||
output.pop();
|
||
output.push(this.address4.groupForV6());
|
||
}
|
||
return output.join(':');
|
||
}
|
||
// #endregion
|
||
// #region Regular expressions
|
||
/**
|
||
* Generate a regular expression string that can be used to find or validate
|
||
* all variations of this address
|
||
* @param {boolean} substringSearch
|
||
* @returns {string}
|
||
*/
|
||
regularExpressionString(substringSearch = false) {
|
||
let output = [];
|
||
// TODO: revisit why this is necessary
|
||
const address6 = new Address6(this.correctForm());
|
||
if (address6.elidedGroups === 0) {
|
||
// The simple case
|
||
output.push((0, regular_expressions_1.simpleRegularExpression)(address6.parsedAddress));
|
||
}
|
||
else if (address6.elidedGroups === constants6.GROUPS) {
|
||
// A completely elided address
|
||
output.push((0, regular_expressions_1.possibleElisions)(constants6.GROUPS));
|
||
}
|
||
else {
|
||
// A partially elided address
|
||
const halves = address6.address.split('::');
|
||
if (halves[0].length) {
|
||
output.push((0, regular_expressions_1.simpleRegularExpression)(halves[0].split(':')));
|
||
}
|
||
assert(typeof address6.elidedGroups === 'number');
|
||
output.push((0, regular_expressions_1.possibleElisions)(address6.elidedGroups, halves[0].length !== 0, halves[1].length !== 0));
|
||
if (halves[1].length) {
|
||
output.push((0, regular_expressions_1.simpleRegularExpression)(halves[1].split(':')));
|
||
}
|
||
output = [output.join(':')];
|
||
}
|
||
if (!substringSearch) {
|
||
output = [
|
||
'(?=^|',
|
||
regular_expressions_1.ADDRESS_BOUNDARY,
|
||
'|[^\\w\\:])(',
|
||
...output,
|
||
')(?=[^\\w\\:]|',
|
||
regular_expressions_1.ADDRESS_BOUNDARY,
|
||
'|$)',
|
||
];
|
||
}
|
||
return output.join('');
|
||
}
|
||
/**
|
||
* Generate a regular expression that can be used to find or validate all
|
||
* variations of this address.
|
||
* @param {boolean} substringSearch
|
||
* @returns {RegExp}
|
||
*/
|
||
regularExpression(substringSearch = false) {
|
||
return new RegExp(this.regularExpressionString(substringSearch), 'i');
|
||
}
|
||
}
|
||
exports.Address6 = Address6;
|
||
const TYPE_SUBNETS = Object.keys(constants6.TYPES).map((subnet) => [
|
||
new Address6(subnet),
|
||
constants6.TYPES[subnet],
|
||
]);
|
||
const TEREDO_SUBNET = new Address6('2001::/32');
|
||
const SIX_TO_FOUR_SUBNET = new Address6('2002::/16');
|
||
const ULA_SUBNET = new Address6('fc00::/7');
|
||
const DOCUMENTATION_SUBNET = new Address6('2001:db8::/32');
|
||
const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96');
|
||
//# sourceMappingURL=ipv6.js.map
|