UNPKG

tshttpurl

Version:

Http URL and IP address parser and normalizer

412 lines 14.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IPv6 = void 0; class IPv6 { /** * * @param value The object from which to create the new instance. E.g. * ```ts * "192.168.0.1" * "::FFFF:192.168.0.1" * "fe80:0:0:0:202:b3ff:fe1e:8329" * 0xfe800000000000000202b3fffe1e8329n * 0xFFFF08080808n // "8.8.8.8" * ``` */ constructor(value) { value = IPv6.toBigInt(value); if (IPv6.MAX < value) { throw new RangeError('value has more than 128 bits'); } else if (value < IPv6.ZERO) { throw new RangeError('value is negative'); } this.value = value; } ; /** * Parses a hexidecimal string. * @param value String made up only of hexidecimal digits * @returns number represented by `value` * @throws RangeError if the whole string isn't parsed. */ static parseHex(value) { if (IPv6.HEXREGEX.test(value)) { let i = parseInt(value, 16); if (isFinite(i)) { return i; } } throw new RangeError('value is not a hexidecimal digit'); } ; /** * Parses a decimal string. * @param value String made up only of decimal digits * @returns number represented by `value` * @throws RangeError if the whole string isn't parsed. */ static parseDecimal(value) { if (IPv6.DECREGEX.test(value)) { let i = parseInt(value, 10); if (isFinite(i)) { return i; } } throw new RangeError('value is not a decimal digit'); } ; /** * Gets the number of times the specified RegExp matches a specified string. * * @param subStr Regular Expression to search for. Should have * the 'g' flag specified so multiple matches are made. * @param superStr The String in which to search. * @returns the number of times `subStr` appears in `superStr` */ static matchCount(subStr, superStr) { let matchArray = superStr.match(subStr); return null === matchArray ? 0 : matchArray.length; } /** * Casts the value to an IPv6 instance. * @param value Value to cast. * @returns the supplied value if it is already an IPv6, otherwise a new * IPv6 constructed from it. * @throws RangeError if a negative or >128 bit bigint is passed, or an * Error if string that can not be parsed is passed. */ static from(value) { return value instanceof IPv6 ? value : new IPv6(value); } /** * Casts the given object to a bigint. * @param value bigint, IPv6, or IPv6 formatted string to cast to a bigint. * @returns bigint */ static toBigInt(value) { if (null == value) { throw new Error('missing parameter'); } if (value instanceof IPv6) { value = value.value; } else if ('string' == typeof value) { value = IPv6.fromString(value); } return value; } /** * Helper function that parses an IPv6 string to a bigint * @param value String representing an IPv6 Address * @throws Error if the string can't be parsed * @returns bigint */ static fromString(value) { value = value.replace(IPv6.SPACEREGEX, ''); let hasIPv4 = 0 < value.lastIndexOf('.'); if (value.indexOf(':') < 0) { // IPv4 specified, turn it into IPv6 representation of IPv4. value = '::FFFF:' + value; if (!hasIPv4) { throw new Error("Invalid address"); } } let colonColons = IPv6.matchCount(IPv6.COLONSREGEX, value); if (1 < colonColons) { throw new Error('too many "::" in the IPv6 string'); } else if (1 === colonColons) { let colons = IPv6.matchCount(IPv6.COLONREGEX, value); let missingParts = (hasIPv4 ? 7 : 8) - colons; let replacement = ':'; for (let i = 0; i < missingParts; i += 1) { replacement += '0:'; } value = value.replace(IPv6.COLONSREGEX, replacement); } if (0 === value.indexOf(':')) { value = '0' + value; } if (':' === value.charAt(value.length - 1)) { value += '0'; } let hexStr = ""; let parts = value.split(':'); let intVal = -1; if (hasIPv4) { let quads = parts.pop().split('.'); if (4 !== quads.length) { throw new Error('Invalid IP v 4 address'); } for (let i = 0; i < 4; i += 1) { try { intVal = IPv6.parseDecimal(quads[i]); } catch (e) { throw new Error('Invalid IP v 4 address: ' + e); } if (intVal < 0 || 255 < intVal) { throw new Error('Invalid IP v 4 address'); } hexStr += intVal.toString(16).padStart(2, '0'); } } if (8 != (hexStr.length / 4) + parts.length) { throw new Error("Invalid address"); } for (let part = parts.pop(); part; part = parts.pop()) { try { intVal = IPv6.parseHex(part); } catch (e) { throw new Error('Invalid IP v 6 address: ' + e); } if (intVal < 0 || 0xFFFF < intVal) { throw new Error('Invalid IP v 6 address'); } hexStr = intVal.toString(16).padStart(4, '0') + hexStr; } return BigInt("0x" + hexStr); } ; /** * Determines if the object represents an IP v 4 address or not. * ```ts * IPv6.isIPv4("10.0.0.1") // true * IPv6.isIPv4("::ffff:a00:1") // true * IPv6.isIPv4(" 2001:db8:a::123") // false * ``` * @param ipv6 The object to test. */ static isIPv4(ipv6) { return IPv6.IPV4PREFIX === (IPv6.IPV4MASK & IPv6.toBigInt(ipv6)); } /** * Determines if the object represents an IP v 4 address or not. * ```ts * (new IPv6("10.0.0.1") ).isIPv4(); // true * (new IPv6("::ffff:a00:1") ).isIPv4(); // true * (new IPv6(" 2001:db8:a::123")).isIPv4(); // false * ``` * @returns true if this is an IP v 4 address, false otherwise. */ isIPv4() { const isV4 = IPv6.isIPv4(this); this.isIPv4 = function () { return isV4; }; return isV4; } ; /** * Gets the [canonical](https://en.wikipedia.org/wiki/IPv6_address#Recommended_representation_as_text) * IP v 6 representation of an IP Address. I.e. Lower case, without leading zeros and the left-most * largest set of consecutive zero parts collapsed to a double colon: * ```ts * "::ffff:192.168.0.1" * "2001:db8::1:0:0:1" * ``` * @param ipv6 The object representing the IPv6 address. * @param dotDec For [IPv4-mapped](https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses), * indicates whether to use the 'dot-decimal' notation for the IPv4 part. * Defaults to true. For example: * ```ts * IPv6.toIPv6String("10.0.0.1") // "::ffff:10.0.0.1" * IPv6.toIPv6String("10.0.0.1", false) // "::ffff:a00:1" * ``` * @returns The compact IP v 6 representation of this IP Address. */ static toIPv6String(ipv6, dotDec = true) { ipv6 = IPv6.from(ipv6); let ipv6Str = ''; if (ipv6.isIPv4() && dotDec) { ipv6Str = '::ffff:' + ipv6.toIPv4String(); } else { let hexStr = ipv6.value.toString(16).padStart(32, '0'); let hexStrs = hexStr.match(IPv6.HEX4REGEX) || []; let intParts = []; let current_streak = 0; let current_start = -1; let longest_streak = 0; let longest_start = -1; // remove leading zeros of each part. for (let i = 0; i < 8; i++) { let part = parseInt(hexStrs[i], 16); intParts[i] = part; if (0 === part) { current_streak++; if (current_start < 0) { current_start = i; } if (current_streak > longest_streak) { longest_streak = current_streak; longest_start = current_start; } } else { current_start = -1; current_streak = 0; } } if (longest_streak <= 1) { ipv6Str = hexStrs.join(':'); } else { // collapse the longest streak of zero parts to '::' if (0 === longest_start) { ipv6Str = ':'; } for (let i = 0; i < longest_start; i++) { ipv6Str += intParts[i].toString(16) + ':'; } for (let i = longest_start + longest_streak; i < 8; i++) { ipv6Str += ':' + intParts[i].toString(16); } if (longest_start + longest_streak === 8) { ipv6Str += ':'; } } // ipv6Str = ipv6Str.replace(/((^0:)|:)(0:)+(0$)?/, '::'); } return ipv6Str; } /** * Gets the [canonical](https://en.wikipedia.org/wiki/IPv6_address#Recommended_representation_as_text) * IP v 6 representation of an IP Address. I.e. Lower case, without leading zeros and the left-most * largest set of consecutive zero parts collapsed to a double colon: * ```ts * "::ffff:192.168.0.1" * "2001:db8::1:0:0:1" * ``` * @param dotDec For [IPv4-mapped](https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses), * indicates whether to use the 'dot-decimal' notation for the IPv4 part. * Defaults to true. For example: * ```ts * (new IPv6("10.0.0.1")).toIPv6String() // "::ffff:10.0.0.1" * (new IPv6("10.0.0.1")).toIPv6String(false) // "::ffff:a00:1" * ``` * @returns The compact IP v 6 representation of this IP Address. */ toIPv6String(dotDec = true) { return IPv6.toIPv6String(this, dotDec); //TODO: memoize for each value of dotDec } ; /** * Gets the IP v 4 "dot-decimal" representation of an IP address, if it * represents an IP v 4 address. E.g. * ```ts * "192.168.0.1" * ``` * @returns The "dot-decimal" representation of this IP Address. * * @throws an Error if ipv6 isn't an IP v 4 address. (if isIPv4() === false) */ static toIPv4String(ipv6) { ipv6 = IPv6.from(ipv6); if (ipv6.isIPv4()) { let hexStrs = (ipv6.value.toString(16)).match(IPv6.HEX2REGEX) || []; let ints = hexStrs.slice(-4); for (let i = 0; i < 4; i += 1) { ints[i] = parseInt(ints[i], 16).toString(10); } return ints.join('.'); } throw new Error('Not an IPv4 address: "' + ipv6.toString() + '"'); } /** * Gets the IP v 4 "dot-decimal" representation of this IP address, if this * address represents an IP v 4 address. E.g. * ```ts * "192.168.0.1" * ``` * * @returns The "dot-decimal" representation of this IP Address. * * @throws an Error if this isn't a IP v 4 address. (if isIPv4() === false) */ toIPv4String() { let ipv4Str = IPv6.toIPv4String(this); this.toIPv4String = function () { return ipv4Str; }; return ipv4Str; } ; /**` * @returns returns the IP v 4 "dot-decimal" representation if * isIPv4() == true, otherwise the IP v 6 formatted version. */ toString() { return this.isIPv4() ? this.toIPv4String() : this.toIPv6String(); } ; /** * Compares this IP Address to another for order. * @param first The first address to compare * @param second The second address to compare * @returns A negative integer, zero, or a positive integer as first * is less than, equal to, or greater than second. * @throws an Error if either parameter is a string that can not be parsed. */ static compare(first, second) { first = IPv6.toBigInt(first); second = IPv6.toBigInt(second); return first < second ? -1 : (first > second ? 1 : 0); } /** * Compares this IP Address to another for order. * @param other The address to use as a comparison. * * @returns A negative integer, zero, or a positive integer as this * is less than, equal to, or greater than other. * @throws an Error if other is a string that can not be parsed. */ compare(other) { return IPv6.compare(this, other); } /** * Determines whether the arguments represent the same address * @param first The first address to use as a comparison. * @param second The second address to use as a comparison. * @returns boolean */ static equals(first, second) { let equal = false; if (null == first) { equal = null == second; } else if (second != null) { try { equal = 0 == IPv6.compare(first, second); } catch (Error) { // use default of `false` } } return equal; } /** * Determines whether the passed in value represents the same value as * this instance * @param other The address to use as a comparison. * null or malformed addresses result in false being returned. * @returns boolean */ equals(other) { return IPv6.equals(this, other); } } exports.IPv6 = IPv6; IPv6.MAX = (2n ** 128n) - 1n; IPv6.IPV4PREFIX = 0xffff00000000n; IPv6.IPV4MASK = 0xffffffffffffffffffffffff00000000n; IPv6.ZERO = 0n; IPv6.HEX4REGEX = /[0-9a-f]{4}/gi; IPv6.HEX2REGEX = /[0-9a-f]{2}/gi; IPv6.HEXREGEX = /^(\d|[a-f])+$/i; IPv6.DECREGEX = /^\d+$/i; IPv6.SPACEREGEX = /\s+/; IPv6.COLONSREGEX = /::/g; IPv6.COLONREGEX = /:/g; //# sourceMappingURL=IPv6.js.map